From 22beeb5181ec7be68b9705cd9bd0e7803f7f62c2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 15:39:23 +0100 Subject: [PATCH 001/583] The dominator tree of a CFG can now be derived. Former-commit-id: 023fbd1403ec01e4d13583bfcc905b2a36e538a3 --- .../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 4a36107329..04dd976c6e 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 @@ -729,6 +729,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 54b5d09dee..c3512905af 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.AnyFunSpec import org.scalatest.matchers.should.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 AnyFunSpec with Matchers with BeforeAndAf 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 AnyFunSpec with Matchers with BeforeAndAf } 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 e9c81f7b53fb4400d0621128eebd807a02e035ca Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:08:03 +0100 Subject: [PATCH 002/583] Implemented an analysis that determines the string constancy level as well as the possible values for a read operation of a local string variable. Former-commit-id: 8c1ebe92d49397cebda08258b2c44aacabd021cb --- .../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 801691b3e166f116e33d39a36657e09fbcde617f Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:10:50 +0100 Subject: [PATCH 003/583] 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. Former-commit-id: 1cb73d37f2b946f83ed7673bdcbe3aca4735f692 --- .../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 f1812528d7d9d6af69bd4372ca6b90341f843a41 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:40:16 +0100 Subject: [PATCH 004/583] Implemented the matcher for the LocalStringDefinitionAnalysis. Former-commit-id: c9ec18139b0b5ff2848e36d603c40bee9f1bd7a9 --- .../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 6ba8f1f30f036fe7e6de752a96fd45e375778462 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:40:50 +0100 Subject: [PATCH 005/583] Renamed and commented a function. Former-commit-id: 138b099e3d4b196a19141661a30e997652ba11b0 --- .../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 56be19a9ca26785d5059230fc70d59ad53acd1d9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:57:42 +0100 Subject: [PATCH 006/583] Restructured the code for the StringDefinitionAnalysis. Former-commit-id: 031bef0d5a43012e718c778385dea8c114190a4b --- .../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 72d6cb7d4855900d1ae2215aa6a59c2ec2c0d193 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 18:03:40 +0100 Subject: [PATCH 007/583] The StringDefinitionAnalysis can now handle multiple definition sites. Former-commit-id: db277be7a9a3518e1cbb10d7c1824d27a5191e9c --- .../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 c30f5f0e78df45bf92bcfe6ae1e6a9f1e45eca2f Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 18:12:07 +0100 Subject: [PATCH 008/583] The StringDefinitionAnalysis was changed in a way that it now builds string trees. Former-commit-id: 120c3454c8b1728cd00bec864ff796c4b0d7da39 --- .../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 534012ab74ce9385738e35db5a10fe6fd0994f60 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:21:49 +0100 Subject: [PATCH 009/583] 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) Former-commit-id: 40416a92f121b01fd358ac74006a3993aff69623 --- .../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 ef05ab6ae42a469d23c318dc0aaa739a41d0a3b7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:24:02 +0100 Subject: [PATCH 010/583] Some if-else test cases work and integer values are recognized as well. Former-commit-id: fb567ed252cf2caf12a6ba9df0e1eefc27963770 --- .../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 d6d0c0bfdb2d043c4bd237431b9032f89c80b6d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:26:38 +0100 Subject: [PATCH 011/583] Improved the construction of string trees with regard to if-else blocks. Former-commit-id: 493de5ada461fbf57afd0cd6f5f556bc739d5fc5 --- .../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 b71aa4c546fab675bddc55d0d0a3fb31faac33b1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:27:22 +0100 Subject: [PATCH 012/583] Extended the StringTree construction to cover another inner if-else block structure (see added test method). Former-commit-id: 73c65be2f8e47d36b939953630c6fe6bcf4e163e --- .../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 4058546e991d953d1a82f29284a024f6c4b2c69d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:27:48 +0100 Subject: [PATCH 013/583] The string approximation can now recognize the case when an element is within a loop. A simple test was added, too. Former-commit-id: 8a463c26474a36c4366f4c35535b563297d483e0 --- .../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 22254faa5675667b7d786dbd553b3b6ea40bb0f9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:29:34 +0100 Subject: [PATCH 014/583] 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. Former-commit-id: fd83207f014de02f3ac7251ac1ea5a2d1e89ccd1 --- .../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 c92ecc9a033442421c1c581b4ad4cf844dc61d2e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:33:27 +0100 Subject: [PATCH 015/583] 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). Former-commit-id: a1e62b07691f0f12bae18e92ec0856bdb260785e --- .../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 894308083ebae0a102be1be7703cde1799ef65c3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:35:10 +0100 Subject: [PATCH 016/583] 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 Former-commit-id: 75080bc1572addf620f6f098cd2d296c0a533811 --- .../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 083b8c35ecc46356c3ef01d0972c214a1d55e75d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:37:04 +0100 Subject: [PATCH 017/583] Started working on the case when several consecutive 'append' calls are made. To finish this, a dominator tree is necessary. Former-commit-id: 84d6e883548c372aa9b484f93565d1db01d6ed62 --- .../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 8807e58a9b2d7844ed3d8403982b7e3463e43252 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:39:46 +0100 Subject: [PATCH 018/583] By making use of the dominator tree, the test case 'directAppendConcats' works now. Former-commit-id: 515ae1e3117128fb4327cbf5663ddebff657e34b --- .../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 83a1b27e4004bd71f24ee9b9c31a6d066969c375 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:40:18 +0100 Subject: [PATCH 019/583] Extended the string definition analysis which now supports the extended test case in TestMethods.java. Former-commit-id: 20be52d504f9e2904a355489e3b1b9983a15ee51 --- .../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 20ea4586481ba6b611e6d460b7f58f90dc12b7c5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 9 Nov 2018 14:09:32 +0100 Subject: [PATCH 020/583] Moved the 'doesDominate' method into the DominatorTree.scala file. Former-commit-id: c61ade378a1d1b9cbc9b144675b439da11c53bb7 --- .../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 5ca9a66c2f..486d385031 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 2b27d23ae7cd09487188685838e1105eac6fcc80 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 9 Nov 2018 21:24:00 +0100 Subject: [PATCH 021/583] Implemented an algorithm for finding natural loops that makes use of dominator information (and is not as "primitive" as the previous one). Former-commit-id: e3ef140e0d88bea2491e988e3980e4f779daef39 --- .../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 04dd976c6e..357ba46a16 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. * @@ -744,6 +747,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 56faba34623b5b8d65b58c4d39f3f31dfb728f02 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 10 Nov 2018 16:48:05 +0100 Subject: [PATCH 022/583] Minor changes on the LocalStringDefinitionMatcher and the related file StringConstancyProperty. Former-commit-id: ff28465a07ad9e906d86e3f31fab7a0ae73a32c4 --- .../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 ce5ad668ebc4ec8e1d967cbd7f3116e46a5a1907 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 10 Nov 2018 16:49:25 +0100 Subject: [PATCH 023/583] 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). Former-commit-id: fcabddac3334d855f36553e5c1614470602c43d2 --- .../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 9c3f1055390eac611239f9f506160354cc39d68b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 11 Nov 2018 16:19:34 +0100 Subject: [PATCH 024/583] 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. Former-commit-id: 34d43a98918bde28d90d690d482814c2ea34770d --- .../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 2d7c13f15496fd478c9b287048a789c826981938 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 12 Nov 2018 14:48:33 +0100 Subject: [PATCH 025/583] 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. Former-commit-id: b1864a49ef432e6096533e83cd5a73d2f8b33f38 --- .../string_definition/TestMethods.java | 27 +++++++++---------- .../NewStringBuilderProcessor.scala | 14 ++++++++-- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 27 ++++++++++++++++++- .../properties/StringTree.scala | 16 +++++++++-- .../org/opalj/graphs/PostDominatorTree.scala | 23 ++++++++++++++++ 5 files changed, 88 insertions(+), 19 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 357ba46a16..ebf14ea273 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,7 +7,9 @@ import scala.reflect.ClassTag import java.util.Arrays -import scala.collection.{Set => SomeSet} +import org.opalj.collection.immutable.EmptyIntTrieSet + +import scala.collection.{Set ⇒ SomeSet} import scala.collection.AbstractIterator import org.opalj.log.LogContext @@ -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 @@ -795,6 +798,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 dc99d9d8dc..43e0b5ccfb 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 43551bfc962e76ef78bdb6ce94b38a56c4133838 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Nov 2018 09:18:52 +0100 Subject: [PATCH 026/583] The construction of the PostDominatorTree was partly incorrect. Former-commit-id: 4666365ce85ab32a51a21504a0c9db1a9ec99b76 --- 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 ebf14ea273..ced1eaf75b 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 @@ -804,7 +804,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 9c9dc3423d74ea37e54908debbe58e406e225e50 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:43:49 +0100 Subject: [PATCH 027/583] Use a question mark instead of a plus sign to indicate that a string can occur zero or one time. Former-commit-id: 1e14fb587628b44fab5a0d9fcd33967b1788f1dc --- .../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 f5b9aff2c7f497ad7c68b3889e395d2e69466953 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:45:40 +0100 Subject: [PATCH 028/583] Between the | sign (coming from ORs) there should be no space (otherwise ambiguities can arise). Former-commit-id: c77bca44a93bc04b93391064260145145b5127d5 --- .../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 c20e6688c3cc6d3ebc0d055d72723da2fb655995 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:55:43 +0100 Subject: [PATCH 029/583] 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. Former-commit-id: 46c9986402ca5078c9d254e1d261160e80f09582 --- .../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 1273d5c443f85b0313324f5371319f50e504cfcf Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 17:18:37 +0100 Subject: [PATCH 030/583] Added more test cases (that we talked about in the last meeting) and which are currently not supported by the analysis (thus, commented out). Former-commit-id: 96bc1686e54bb258b0e1413b919494fac6afc4a1 --- .../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 b45c6401c86f4f5d6eb5940daa6c80a98b025aa7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Nov 2018 12:19:48 +0100 Subject: [PATCH 031/583] Implemented a way to find all paths in a CFG from a set of given starting points. Former-commit-id: 1728aa76c9ee6e10645380b36f9d3204bafe1e80 --- .../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 bfa79ff0bbaeae053d8bef677d0037654643329b Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Nov 2018 12:21:08 +0100 Subject: [PATCH 032/583] Added a method to find all initialization sites of (StringBuilder) objects. Former-commit-id: 8a34a37ae8d43b848fed268d297fc58dc2beed65 --- .../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 bd39a44a7f1aa0f4d059259f57af1c3acc84a09c Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 22 Nov 2018 22:18:43 +0100 Subject: [PATCH 033/583] 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. Former-commit-id: 34372b749c4638eeb196162f89bbf0619e22b03c --- .../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 452a2082b6dccca1c8933d6ce43cded0a73291ec Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 23 Nov 2018 12:00:54 +0100 Subject: [PATCH 034/583] (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. Former-commit-id: 2cf13b59cabdde93ebb7a4b45604c977a9b984b9 --- .../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 1670baaaf92f982acb281384f247a5bd573e659f Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 23 Nov 2018 17:48:31 +0100 Subject: [PATCH 035/583] Refined the NestedPathTypes. Former-commit-id: 7afc97552552d69fcff5bb8bb2d818e74736d6b9 --- .../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 4b232f2b33a56a2676e3eec7922147d3cda530ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 25 Nov 2018 17:31:25 +0100 Subject: [PATCH 036/583] Implemented the path transformer along with classes that provide interpretation functionalities. Former-commit-id: 3f800aef4b301c5c4e8773a1ae6d9014142beeee --- .../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 4ddfedf8d81f1d020fb100f4323cc7df399384e0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 11:17:01 +0100 Subject: [PATCH 037/583] The current interpreters were not able to properly handle `append` and `toString` calls (which they do now). Former-commit-id: 5fb30638ed2b45fceee55fbc41041b701d652e23 --- .../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 7c8269682830ab249fb32f170c8f2729e15794e6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 11:55:35 +0100 Subject: [PATCH 038/583] The `getAllDefAndUseSites` function had to be refined. Former-commit-id: 760ebac71ff8b64383fcc31f48a4c4986d71a5da --- .../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 c07ebaa53fee5b87c438d9cf41ebb4fc1769eaa1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 13:12:35 +0100 Subject: [PATCH 039/583] Added a NonVirtualMethodCallInterpreter. Former-commit-id: 1fe84a8758b49a92012829885ad6618713ce1bec --- .../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 1263d6894e2e72d6e770ae33a5805aa2ab923714 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 14:33:59 +0100 Subject: [PATCH 040/583] The pathToTreeAcc function of PathTransformer had a bug. Former-commit-id: d0d9d9c568154c5ac5fdc4166145df0940015583 --- .../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 1d0db5ac1674ae94abe5909658fa2ed030ceb784 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 15:30:17 +0100 Subject: [PATCH 041/583] Had to refine the pathToTreeAcc function along with the interpretAppendCall function to correctly distinguish Concats and Ors Former-commit-id: ce6679a0bd682cb57fef447e650fc9c29cb9c430 --- .../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 289edc66cf4d08bed83baa04600df9c3f45c3968 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 16:55:59 +0100 Subject: [PATCH 042/583] `append` calls with an int or float value are now processed as well (in a very simple way though). Former-commit-id: df8f2c6f4acd9277446ed90f5e78e4edd37b04fe --- .../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 73d4f186b45154f8615bb6d8d98a948a6b32ca78 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:03:43 +0100 Subject: [PATCH 043/583] Updated the LocalStringDefinitionAnalysis to work with the new interpretation approach. Former-commit-id: aaf0a0a4d1eb3cd240f82f672324921406a85b48 --- .../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 6e53600b34fa7b1cc73d5bf872fc0c720fc71965 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:04:40 +0100 Subject: [PATCH 044/583] Minor changes on the test cases. Former-commit-id: be5f729dd05545e32481795472d278ff54a532a9 --- .../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 809c193ba099271f05615b44444b29d280635422 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:26:19 +0100 Subject: [PATCH 045/583] Cleaned the code and refactored a bit. Former-commit-id: 8fc8c9502226957f6d555ebcfef46d3097e46fbe --- .../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 de0d13350896ddb59e51c73727f31b37bb6f5f5c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:25:46 +0100 Subject: [PATCH 046/583] Enabled a further working test case. Former-commit-id: 4ebd3e523184d046b83a44c9fa68db856d004d24 --- .../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 f99a9ffeccc15b3d5cd4f0a299f0a2575cb94499 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:40:42 +0100 Subject: [PATCH 047/583] 1) Early stop analyzing when the desired code instruction was reached 2) Enabled further working test cases Former-commit-id: 4193d9762b2413281fca75fbcdd6f9a731ea90f2 --- .../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 e5b08097dba43a5192bc629aa19c889ff4c78c5c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:53:39 +0100 Subject: [PATCH 048/583] Added support for StringBuffers and added an example test case. Former-commit-id: b483e582ebcd64d32fced7dd79ad3ae5fb0b5456 --- .../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 d17b5efcabfbbdcf44480a9656de0abca63f6a66 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 22:07:09 +0100 Subject: [PATCH 049/583] Fixed a minor bug in the 'pathToStringTree'; now a while-true test case works Former-commit-id: 8095ecc2784c14470131361900c6227c15a078f1 --- .../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 8309e636cdc616071f11ddf7d10152f2f3af08de Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 12:16:12 +0100 Subject: [PATCH 050/583] Improvements on the VirtualFunctionCallInterpreter. Former-commit-id: ecffaad221a124c4567150d62156d6f9bb971398 --- .../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 98e0008c577bcf0788596eec833f92cc59f9b35a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 13:58:11 +0100 Subject: [PATCH 051/583] Reformatted and changed a comment. Former-commit-id: 6b6d02f124d2451adc41f798608d9c235417b356 --- .../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 cdc6b59f6ff4b5b38a2a2ead1bb0ce79b34a8f75 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 14:01:50 +0100 Subject: [PATCH 052/583] Fixed a typo. Former-commit-id: 33842c0a90973d2cca1c3d4a21c6c1385526918d --- .../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 7882ecdf903136ca3d500274818c797d7d0a2591 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 14:02:06 +0100 Subject: [PATCH 053/583] Slightly reformatted the file. Former-commit-id: 60450df1459bf735f908e02e6003eeae6054a461 --- .../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 0a92dd7786e86b7eca9391e580510bff9f908f1f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 16:55:05 +0100 Subject: [PATCH 054/583] Improved the procedure to find all paths and updated the (working) test cases accordingly. Former-commit-id: 2bc2c994835661683e960338857e7cca76d23ca0 --- .../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 22e06747bf25c897d71caa9f666903070c11b94e Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 09:39:55 +0100 Subject: [PATCH 055/583] Removed an unnecessary parameter. Former-commit-id: 3af59e4d98238ec7b0f7e0cdc76ea95ade23884e --- .../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 b2cde130780c04e372578df2b8d42a49438c35e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 10:22:23 +0100 Subject: [PATCH 056/583] The DefaultPathFinder now makes use of an IntArrayStack. Former-commit-id: 230264592f12eda0d8b41478824ddcd242f0e54b --- .../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 8decebe3269d05254d1841233f09d0c9974d83e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:01:56 +0100 Subject: [PATCH 057/583] 1) Added support for static function calls. 2) Extended the valueOfAppendCall procedure as it did not always capture all possible values. Former-commit-id: b7653ac99d77bc1447336ed7814875ae6f3d1191 --- .../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 3d1dae57f746a832040c2cdcd70feed92d31529f Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:22:34 +0100 Subject: [PATCH 058/583] Added support for exceptions. Former-commit-id: 7b1da3b24ac23dcbdba213f588571eb07a9c518c --- .../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 bf2c465ddb1981865ad2bce0da20cd8aefa04b55 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:22:53 +0100 Subject: [PATCH 059/583] Reformatted the file. Former-commit-id: 44a8766cf009683aa0df1ecbdb701f1d16435030 --- .../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 bde8fa1403eeab2b8daa2be2595c8da085ca08dc Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:23:07 +0100 Subject: [PATCH 060/583] Slightly changed a comment. Former-commit-id: 2ce2bf6f6caa172bb588cdc752502b5c3e6867d8 --- .../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 b13d811d415a07126c442d5d4f99d8b4763961ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 10:09:23 +0100 Subject: [PATCH 061/583] Extended the support for exceptions (now try-catch-finally's are supported as well). Former-commit-id: b1399d2d7af26009b8a9030ccc37ba762bb293fb --- .../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 3f2ef89ab111e1cdbaa018051193ad108d901c7d Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 10:09:34 +0100 Subject: [PATCH 062/583] Fixed a condition. Former-commit-id: 962b7fefe0af5be667f24d3d6283ae1435dcb352 --- .../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 274827d1aeffcb4b5130b1b24ee09de84b65358f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 22:56:21 +0100 Subject: [PATCH 063/583] Added support for String{Builder,Buffer} clears. Former-commit-id: 026bda1d842c615f636457d3810298d891ae781a --- .../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 5e77e305f8a08851adcc4f530e39722cdc5ab197 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 10:25:10 +0100 Subject: [PATCH 064/583] Reformatted and refactored code belonging to the processing of String clear operations. Former-commit-id: 95bf45acb51f2cd3c55cda942d8c2d878a035bb4 --- .../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 c5b4728453c6898690a0408a0040fc3ba75b9a5b Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 10:55:55 +0100 Subject: [PATCH 065/583] Added another test case for the 'clear string' case. Former-commit-id: 5f5ccf22f4cafb53908d52c6c5d4e1f66f127088 --- .../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 62192af5e2c31b97405c27fc115c17e42714302b Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 20:16:09 +0100 Subject: [PATCH 066/583] Fixed a bug. Former-commit-id: 7d2223f72ec7b31e3783620e69840f3b60db05f1 --- .../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 bc556ec9ac8009a116b3b7313e415d1e624d8000 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 20:17:01 +0100 Subject: [PATCH 067/583] The string definition analysis now supports 'replace' calls of String{Builder, Buffer} as well. Former-commit-id: baa5a21277689a99f24ca89c32dadf3d44ceaf91 --- .../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 bd068f458db993611e4fe8f0368357480921924a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 14:16:51 +0100 Subject: [PATCH 068/583] 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. Former-commit-id: b2f4002a105380ec42fd7b5e0f05280f5e397756 --- .../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 bf233575650a043330e1fbf5672ff520c3f21911 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 14:23:15 +0100 Subject: [PATCH 069/583] Made the 'pathToStringTree' function less defensive. Former-commit-id: 83d02d6ec6f78ad2db7bf31629aa9ba0c55f118d --- .../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 e4c548b9a05192db89fc76ee68bf8565d3ff9b8f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 16:12:12 +0100 Subject: [PATCH 070/583] Reducing a StringTree now allows to pre-process the tree. Former-commit-id: fdc098ef9186303b136e4e5759237f08005b4ac0 --- .../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 45a62b1402ee9e7c0c3881713e939a060a92b8d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 16:21:51 +0100 Subject: [PATCH 071/583] Defined and toString method of StringConstancyProperty and made sure it gets printed when a test fails. Former-commit-id: d75c1b0a4cebdbb9940da7531eb75ce249215857 --- .../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 cd933dacf1faaa78d157ce9bda3d213da029e351 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 16:34:40 +0100 Subject: [PATCH 072/583] Collapsed a couple of test methods. Former-commit-id: 418ddafabb3cd700abc175d925016d0bf86eb0a1 --- .../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 05f778a63ba942842b6070ba04f2d478807c0291 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 16:56:02 +0100 Subject: [PATCH 073/583] Added a test method with 'breaks' and 'continues'. Former-commit-id: e012202cb2c27ef7bb69c605edc88bba547d2fff --- .../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 912c40a70189df237a4eb397f58c838da58a50ab Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 17:09:49 +0100 Subject: [PATCH 074/583] Added an example where in the condition of an 'if', a string is appended to a StringBuilder. Former-commit-id: cdab9e1698e1423619ef3f28ed4ff0e23bf138c3 --- .../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 9645765c0f1d61043027deb5c8efa15753c05ebb Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 7 Dec 2018 13:50:15 +0100 Subject: [PATCH 075/583] Made the 'makeLeanPath' less defensive. Former-commit-id: 766767d8d375bae19dbcaf567e1ae2c9097c0de3 --- .../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 aafe5d57f3dbae21df53f5257c6127dde69f31db Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 8 Dec 2018 21:27:33 +0100 Subject: [PATCH 076/583] Extended the LocalStringDefinitionAnalysis to support the analysis of another string variable in order to produce (more) correct results. Test methods were added as well. Former-commit-id: 35a15f20ec4655f2a7dff173301e7cb36b45cbd1 --- .../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 d3fadade06be14237135c7ae856f6792b579069e Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 9 Dec 2018 10:49:57 +0100 Subject: [PATCH 077/583] Added a comment. Former-commit-id: 6495f049bb71dc0a81138b53b75f43e918e3ca99 --- .../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 e4ae4145ae404064aafb923e27a6fa9eed40801b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 11:18:42 +0100 Subject: [PATCH 078/583] Removed unnecessary code and formatted the file. Former-commit-id: 3440b69b847c2b825be012357963a296a798addc --- .../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 930dfbbe83085e1c670beda5bd5f4032a40a6f54 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 20:07:26 +0100 Subject: [PATCH 079/583] 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. Former-commit-id: 9015ccaa1051308a021b3958bc8ae0d7301cc031 --- .../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 da5e54fde0b11b1fbdec6a65d2176d85350077eb Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 20:14:27 +0100 Subject: [PATCH 080/583] Extended the class documentation. Former-commit-id: 6690f04fe9fa2a9ecb0e37d36ac798ca319b10de --- .../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 467bab62ee999492029e7a1312f65dd5f278fc36 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 12:21:14 +0100 Subject: [PATCH 081/583] Improved the analysis runner (it captures more relevant calls (now it should capture all relevant calls) and is much faster). Former-commit-id: eeffe56b7a9c2bf993cf0d35bf77d603304e7fa7 --- .../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 f5fbfab8ca61817311530815c535f88e155c82a9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 14:49:45 +0100 Subject: [PATCH 082/583] Directly query the property store after a new analysis was started. Former-commit-id: a44a11f280a4b07e9b8b469505eb157838ee929b --- .../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 713b61fabe44cc3827f82ebe9e49e9ad19b9914b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 14:54:43 +0100 Subject: [PATCH 083/583] Refactored the pattern matching. Former-commit-id: 121c566301f624c9ecc9e9f5672083a957842968 --- .../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 3271647d2fccb4663c3d9e221f8fb9ca6d637e2d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 19:50:25 +0100 Subject: [PATCH 084/583] 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. Former-commit-id: 84571be491290129c08f99c8e082f24d6343acf6 --- .../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 9b440b62c89667c80ee0b950bf9213a6acb6e6b4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 20:22:49 +0100 Subject: [PATCH 085/583] 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). Former-commit-id: 54e11d89998ee35cb6012d2cd1f0c16d858b7a47 --- .../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 7d7ad03434f424b7d42fcd63390a4add671759d9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 18 Dec 2018 23:06:28 +0100 Subject: [PATCH 086/583] 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. Former-commit-id: 3e864640de93a33865c1555bd58df2495121e509 --- .../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 b522f148be84d03b8bdbcd8564ced36bdba6145e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 19 Dec 2018 12:32:08 +0100 Subject: [PATCH 087/583] Updated the findDependentVars routine to avoid an endless loop in case of cycles. Former-commit-id: 835d5788e3e7af974e190822de21c87babc695d4 --- .../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 961e7ae4adb67a84fd4297c4256345cec5fef835 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 23 Dec 2018 11:16:35 +0100 Subject: [PATCH 088/583] Added support for the "crissCrossExample" test case that was activated. This required refinements in different routines. Former-commit-id: e840d32c4b8d7dc8c9b6de3a30922fa0cf83519f --- .../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 7a993bdbbfe60b4e3071bcae37f848c7e7d53b95 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 28 Dec 2018 12:39:51 +0100 Subject: [PATCH 089/583] Added a test case which uses a "throwable". This required minor changes in the path finding procedure. Former-commit-id: ab363a1ffb9853b3affbcc4643f3ee41e708bb06 --- .../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 07e8f811306f306e4ae5a2caa8ddf82681ed7357 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 28 Dec 2018 21:30:03 +0100 Subject: [PATCH 090/583] Needed to minorly change how the StringAnalysisReflectiveCalls uses the analysis. Former-commit-id: 389f03a8d7aa0cbb9d1fa9e8187533b026012482 --- .../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 43c2c95e2216e3b5721ff68abb204a502925b828 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/583] Added an analysis which collects which methods of a class are called within a project and how often. Former-commit-id: 520fe7e6128814f221bbb85442d5e38c00613c34 --- .../support/info/ClassUsageAnalysis.scala | 117 +++++++++--------- 1 file changed, 58 insertions(+), 59 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 bf9db44803..7ba2ccd4bd 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,16 +70,11 @@ 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) @@ -139,39 +138,39 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { } else { false // default is coarse grained } - (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 _ => + 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 d75e6f1d07bcc871e86c003fd9a09ad3452af91a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 13:19:46 +0100 Subject: [PATCH 092/583] Added support for the evaluation of method parameters (pure strings as well as String{Builder, Buffer}). Former-commit-id: ab8e3e5b2df4d8db64a06b6fcdba92208bd296ee --- .../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 cf914b35327dd658e942007f8d5a7f420e5c67c3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 16:48:09 +0100 Subject: [PATCH 093/583] 1) isRelevantMethod was not 100 % correct; 2) Had to change how to add items to the property store (as the interface has changed) Former-commit-id: 08307bf9617954dfd6635a44f2e39b8d01752399 --- .../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 d105ab812f7d1e62c68e0a4b415bbbd37784cf69 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 17:00:49 +0100 Subject: [PATCH 094/583] Refined the reduceMultiple method. Former-commit-id: 5f26fa765243d1295955a9009ff2b95ec8052046 --- .../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 757b029169f9122489ca85de48d14eb90ce7e0fe Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 2 Jan 2019 17:45:20 +0100 Subject: [PATCH 095/583] Extended the analysis in the way that it can now be configured using program arguments. Former-commit-id: fc4f6604788ca8f5a7fdfa569bbd7b2b162b14c5 --- .../opalj/support/info/ClassUsageAnalysis.scala | 15 +++++++++++---- 1 file changed, 11 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 7ba2ccd4bd..caab03fef4 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 @@ -145,6 +151,7 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { + getAnalysisParameters(parameters) val resultMap = mutable.Map[String, Int]() val tacProvider = project.get(SimpleTACAIKey) From 730519a0d275f6ff8d26500b90c62cbb64ec00b4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 5 Jan 2019 16:26:58 +0100 Subject: [PATCH 096/583] More refinements were necessary to handle method parameters properly. Former-commit-id: 7a895ba5c67f7d0ad020ffc71186b839b6b3d0c0 --- .../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 99e99c20ab9bcf882435ce7f92fd980e7a88a064 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 6 Jan 2019 20:04:40 +0100 Subject: [PATCH 097/583] Refined how (natural) loops are detected. Former-commit-id: 769defca22601dcb24a9e079f9b59479940dfa50 --- 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 ced1eaf75b..1adbd9a7e1 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 @@ -781,10 +781,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 78314a28f5aa730873cea704f0d92cd42d3b6929 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 11:43:18 +0100 Subject: [PATCH 098/583] Slightly refinement on the algorithm. Former-commit-id: d138e1eae72ce695f79f8e0887655f7ffb51ed03 --- .../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 b22212627c6359dba38dcb60f141e880c4374081 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 13:52:13 +0100 Subject: [PATCH 099/583] 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). Former-commit-id: 0707def4820bc4d07545fa324103d7b307aaf4b0 --- .../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 5d0d7a6654ba3eba552a6c59cc61544b793a40cf Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 14:29:39 +0100 Subject: [PATCH 100/583] "seenNodes" contained one element too much. Former-commit-id: 88833f18cfeceea5e9bd3caf4d89d5b082e739e4 --- .../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 3b7bd38195cdaa9ea31a940b90075e7a6f70f57a Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 16:12:46 +0100 Subject: [PATCH 101/583] Minor change on the loop finding procedure. Former-commit-id: 2386f5300b2102ad6bce42d4a30dcb17dde2d651 --- 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 1adbd9a7e1..103ed4ca21 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,7 +787,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 05ce93bffa37e8a662b0088a23f704916559589f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 17:40:24 +0100 Subject: [PATCH 102/583] "seenNodes" needs to contain the branching site (removing it was not correct). Former-commit-id: 9ccf2a7a7c172830a590e87781bb3710593533f0 --- .../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 2ddb0bb2dcf32e7c955804b418c00be901cd5167 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 17:46:22 +0100 Subject: [PATCH 103/583] Path finding algorithms are now required to find all paths from the start node of the CFG (and not of a custom start node). Former-commit-id: d896542e78647fb85b2e7824e863a65120235809 --- .../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 d35a20ff3c0f927551540fc23b905773bbc775f8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 18:32:59 +0100 Subject: [PATCH 104/583] removeOuterBranching works now recursively. Former-commit-id: c629438118eea967c98064d85a4604c67ed59a49 --- .../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 794ac97e9347723ac8f8d68afb4ebe119ef312b4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 19:40:59 +0100 Subject: [PATCH 105/583] Refined the makeLeanPathAcc function. Former-commit-id: 4ca9f9524c458328f83f5a3a98ccbd479042bf88 --- .../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 f6749afbc61ff2bdc8b19ca24f09a021ad5c5a22 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 20:12:33 +0100 Subject: [PATCH 106/583] Methods, which return a string but do not belong to a group of interpreted (such as 'append') methods, now produce "\w" instead of nothing. Former-commit-id: fb148219cc270fef26ecdd68202c3ad4425e393a --- .../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 e768afbcdd37a3e28504251c2dda69bbf45f3c61 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 8 Jan 2019 20:37:15 +0100 Subject: [PATCH 107/583] Needed to add a condition in order to make sure that the correct order is maintained for nested loops. Former-commit-id: 81c41522c040d681e54038ba5a82258393f81b45 --- .../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 3348a5fbac26f06c024ac05191bad831fdfbcc9a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 08:57:10 +0100 Subject: [PATCH 108/583] Added an explanatory comment. Former-commit-id: 6c0f35bb74c74a5fe18032d77954338ea7358f89 --- .../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 d1c4ae6eade2fc40b89acc14f5988ff7a6394cfd Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 10:25:59 +0100 Subject: [PATCH 109/583] 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). Former-commit-id: 6f9535188d88ed9e6efbbf6ae4d52fc034a5af17 --- .../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 370370fd527910b880a5e0aae5de3f82449ae1e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 15:20:04 +0100 Subject: [PATCH 110/583] Added support for interpreting integer values (only "IntConst" at the moment). A test case was modified accordingly. Former-commit-id: 8f3158991dcc1de377141838b61a2dc8c72cb320 --- .../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 d49ecf322ffbb3fcedbf8973e1b8f5858f9be3c2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 15:58:31 +0100 Subject: [PATCH 111/583] Added support for characters. Former-commit-id: 4bfbfa7e8cef74b229fdb6aae231f0c03867eb5d --- .../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 8c794889e8adcdd03bd297299f9b541f0a520f64 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 19:31:19 +0100 Subject: [PATCH 112/583] No need to compute the three address code twice. Former-commit-id: 9dfcf1b6b888fb4617f6900c5d55fdbf2067a4f2 --- .../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 e4c5370803325fa165404a7812f86cba27198179 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 10 Jan 2019 17:13:15 +0100 Subject: [PATCH 113/583] Changed the DefaultPathFinder to early stop when the required paths have been found. Former-commit-id: a7f24045e62f5449de891be376e211b2fca5c779 --- .../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 5e0ccdd0e8a73655fb87b7d19145bbd6a7b4ab1e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 10:01:42 +0100 Subject: [PATCH 114/583] Added the current state of the runner that was used to get numbers of the usage within the JDK. Former-commit-id: a7dc2a807ecc93ce0d9022c71bb7cfc33f6c77a6 --- .../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 cde4bc65560710ba957241892ce7bbf924b29e38 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 17:12:09 +0100 Subject: [PATCH 115/583] Added primitive support for static field read operations. Former-commit-id: 046c0d3207798295d436bc1a8bcca237319a3903 --- .../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 32a19298ef860dbba4d382f1d68f7724dfbcd88e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 17:39:05 +0100 Subject: [PATCH 116/583] Generalized the Array(Load)Interpreter to also support ArrayStores. Former-commit-id: 4bad4fb7dfda6c0f611a1391f3aebd0a7834904c --- ...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 b214869af2b1a7bcce88c70c6cd3bc972a9fa3a3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:14:57 +0100 Subject: [PATCH 117/583] Refined the doesPathExistTo method. Former-commit-id: a031b0af89018772ad85e4d823d60eba3af5b451 --- .../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 c164d3ec66ac8123419831ca552cc3eb3de7b5ea Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:15:50 +0100 Subject: [PATCH 118/583] Extended the path finding procedure to recognize when it is started within a try-catch block. Former-commit-id: cee16d52251e7d9ab43e0d74fe1f633e7cbdd71e --- .../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 d6730ed8b887312231fd35301c6d886ee9b1d362 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:22:03 +0100 Subject: [PATCH 119/583] 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) Former-commit-id: 65486a3fd38359841375bb13c37545bf5fd8c9ba --- .../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 01e1ab0433d6071c1a8f5a6509362d56c7c73fe0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 17 Jan 2019 17:24:47 +0100 Subject: [PATCH 120/583] Removed a condition (some loop headers were not correctly identified). Former-commit-id: 7dbd21419b76081ae84d5afef237fe40ca4122e8 --- .../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 0ba471844ce18c52fc77d07b83d9c6d6ea35e237 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 17 Jan 2019 17:28:14 +0100 Subject: [PATCH 121/583] Started with the new algorithm. Former-commit-id: d021cbca2f7dc7216bed05deb14ed3ad526c4b78 --- .../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 2a81060d2f82db933837fa7f7465ac5cdb0d758a Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 21 Jan 2019 20:22:16 +0100 Subject: [PATCH 122/583] Finished re-writing the path finding procedure. Former-commit-id: 85718adadd632656853d3ecde263023c9c0fb3f5 --- .../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 fcfa057d7b61e5c239b221cb04125400b66b4274 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 22 Jan 2019 11:26:36 +0100 Subject: [PATCH 123/583] 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). Former-commit-id: ef4b31cfe009d993c6afbea5c7ec969638fef151 --- .../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 dd14a92aa0bb5076fc19420d14a9f8183a1df2a7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 09:33:08 +0100 Subject: [PATCH 124/583] Had to slightly refine the interpretAppendCall method (for the reason see the comment). Former-commit-id: d63b127d73731f1314211721b565cc00dbf6d0c1 --- .../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 f2a06a84596f08e150312e4342184906f37fce20 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 09:33:47 +0100 Subject: [PATCH 125/583] Various minor fixes on the path finding procedures (now the whole JDK can be analyzed with the re-implementation). Former-commit-id: f117e8cde8689878867182621a4d8a32cbd3751f --- .../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 b8186dc552760075186cd24f646c74514c217d1f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 17:23:58 +0100 Subject: [PATCH 126/583] Slightly updated the runner (no new or modifier logic, however). Former-commit-id: 23a09cf6892362709fe774cd84784cd8a39b8d58 --- .../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 44adeb6a43b91f7ee16350ea0b3b47176cc8dde5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 17:25:08 +0100 Subject: [PATCH 127/583] Optimized the imports. Former-commit-id: 73eeb8c9b2d826a5dcb2909ff3982f8f6fa54a2d --- .../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 f80b39b785ef706d813894650278645bf78d0be9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 17:09:45 +0100 Subject: [PATCH 128/583] Changes required by the latest 'develop' branch. Former-commit-id: dd440369323b0806e6c747f19b12177efcfe5632 --- .../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 caab03fef4..87f52c97c0 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 cc1214986cc9afcef2cc7e2f2e51f58106a4fe60 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:43:45 +0100 Subject: [PATCH 129/583] Made StringConstancyProperty extend Property. Former-commit-id: 07c07f75af43934a33e0381d5fbbf09d130ae195 --- .../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 695a12dc512b57c8d5b652a539156370bc553ccf Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:44:37 +0100 Subject: [PATCH 130/583] Further changes were required on the StringAnalysisReflectiveCalls file after having switched to the latest 'develop' version. Former-commit-id: 95c60f018019ed8935288d299708ce7a6a875815 --- .../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 a209dbc302045aae8c77ed4f1db34f6936cd92d5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:45:15 +0100 Subject: [PATCH 131/583] Removed unnecessary comments and properly formatted the file. Former-commit-id: 2336d1b05187f2536640b6c0c2f042ccc24c77f1 --- .../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 a0a869319c282c628e60256f3de5bda1b85ebec9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 11:04:45 +0100 Subject: [PATCH 132/583] Renamed classes, files, and packages (to match the same naming conventions). Former-commit-id: 3e3644e2337e5a5bfcbad18790ea1be54f202a48 --- .../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 81d369889abf2c6fcd81245e9a3b2308e38be292 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 15:45:31 +0100 Subject: [PATCH 133/583] Simplified the code. Former-commit-id: 4f8b83d57028fd2dee807e10f06ed9fcffd4e2f8 --- .../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 f9abdc3e16af6e05580db621bc51e0f6fe8760fe Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:33:44 +0100 Subject: [PATCH 134/583] Generalized the StringAnalysisTest. Former-commit-id: a22b9fb62808017840333ab293b77a9501a82b0d --- ...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 8109c419a5ba7c59c9cb9f909270d9bde1b30504 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:45:21 +0100 Subject: [PATCH 135/583] Added a InterproceduralStringAnalysis (currently not interprocedural) and extended the test suite accordingly. Former-commit-id: 8b24a953ed4f31386053ceec9f18a831388afa4b --- .../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 7a62f0eeb655926e01df0892004b5131bc2121f3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:47:43 +0100 Subject: [PATCH 136/583] Renamed the LocalStringAnalysisMatcher file. Former-commit-id: b3edffbbf394916e8436beae7ef7c9b4c439518f --- .../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 c14cfc33d6dca9d07efdbf2a85a9140b5fbe2988 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 16:48:00 +0100 Subject: [PATCH 137/583] Extended the test suite by analyses that are required to use the call graph. Former-commit-id: 478ef70372250024df3762b2436e946f1457bc0d --- .../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 b691b18194947f3026fdb16122ed9e8736677812 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 19:46:48 +0100 Subject: [PATCH 138/583] 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 Former-commit-id: 432568eeece1276f8e47e496e3576853293b6ab9 --- .../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 59fc32b05b6a4e286c8defbbf026b8ec64cbe06b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 20:19:32 +0100 Subject: [PATCH 139/583] Fixed a copy & paste error in the "apply" method. Former-commit-id: 698a3d39aeea793b3e624930550e2aa55510127f --- .../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 b1d1ae7b772e623ef8406e535c6547eab8e0db0f Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 11:37:02 +0100 Subject: [PATCH 140/583] Extended the constructor to take further information required for interprocedural processing. Former-commit-id: 87d686cb32fef8168da96b5346461ad777677008 --- .../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 4671a39611fa25ac77433c52f551d44e911568ea Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 20:27:46 +0100 Subject: [PATCH 141/583] Changed the interface of the InterpretationHandler and AbstractStringInterpreter (to now return a "Result" object). As a result, many files had to be touched. Former-commit-id: a3bb8456b313533b5a88dfa2d6f65ed7c9a1d0e6 --- .../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 2d7e90dcfdf7826de63dd1fe7a49d7b228d4ab12 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 20:28:17 +0100 Subject: [PATCH 142/583] Added the interprocedural counterpart. Former-commit-id: 34a961f6e651671c5ecdaf71884670b8c17326aa --- .../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 78a0b8d5a43c30b35c883ed7576d5a9ea518b467 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 07:36:38 +0100 Subject: [PATCH 143/583] Slightly changed the interface of AbstractStringInterpreter (which, again, resulted in changes in many files). Former-commit-id: e2a2a2188a3ea2e3d8e84e72f0903378abc61777 --- .../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 a8f1072913481aaf5dd7546122ceb51f1a4a2177 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 16:21:38 +0100 Subject: [PATCH 144/583] Corrected the "extractFromPPCR" method. Former-commit-id: 9d9b0ddaf2dbe4e4a5bf66fb83b99b51313a8cdf --- .../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 0f763b924b62bceb0ecd42a9b09bc2b8bfb39d50 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 16:27:33 +0100 Subject: [PATCH 145/583] Continued making the analysis interprocedural. Former-commit-id: 92548ee5213accf8c582f1456922a0cae4195278 --- .../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 056905ee5f5514f8227459eeeba036b8ac411154 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:13:36 +0100 Subject: [PATCH 146/583] Fixed a little but in the local string analysis which lead to the fact that transitive dependencies were not resolved correctly. Former-commit-id: 415a3f6112888d68d5cb5bc486c383a0372854bd --- .../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 ed49de6018555333e9a8fd492ede5414b3736e1d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:21:48 +0100 Subject: [PATCH 147/583] Renamed "LocalStringAnalysis" to "IntraproceduralStringAnalysis". Former-commit-id: d02db169c542f354aff8d2a12751aa4436757553 --- .../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 7fac2cd5a390d6abb4281d6192d645fdb4aeed44 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:28:40 +0100 Subject: [PATCH 148/583] Had to add the "derivedProperty". Former-commit-id: 21b0c0ef597e6e1d9f1e96296f88745cca1197e1 --- .../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 e97c85bb513675d798b1357dc52ea19a01565ec0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:36:30 +0100 Subject: [PATCH 149/583] Renamed the upper and lower bound of the StringConstancyProperty to "ub" and "lb". Former-commit-id: 6bd1fa2b15b19b2e3287f4eeefe47c0f10069bd3 --- .../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 d63c0d615c03a5360753a20fb441348b31799bfb Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Feb 2019 08:35:20 +0100 Subject: [PATCH 150/583] Started added support for parameters (context-sensitive). Former-commit-id: 3c3df01d80ebd926c88268caf310d3e2895a6c49 --- .../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 f1918274a4045392355520bda744805849f56ee8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Feb 2019 11:08:00 +0100 Subject: [PATCH 151/583] Slightly changed two test cases (1. to use more chars and 2. to use a negative number). Former-commit-id: 9851263d450d6a630c08a3343b3eb52d82b75cca --- .../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 42129855fac1b0a03c6a2865b3fc4b46877a2f7f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 12:34:30 +0100 Subject: [PATCH 152/583] Changed "[AnIntegerValue]" to its RegEx equivalent. Former-commit-id: e844a5eca49a2fe53a565eb0253280c5611d41c5 --- .../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 5b1f6c730ffe2f1e03efa4355c06de134e103c2a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 13:17:52 +0100 Subject: [PATCH 153/583] Changed "[AFloatValue]" to its corresponding RegEx and refined / extended the support for float and double values. Former-commit-id: 04b88cf255b0d4aa26d101d56a15e6af361df3a2 --- .../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 e02dd898dbe3ba0d73e51b6196e5b9b92182b5fa Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 14:16:46 +0100 Subject: [PATCH 154/583] Changed one interprocedural test case in a way to use a method from another class. Former-commit-id: c9b28e132116a47480cf2c9a2821af8e61c6aaea --- .../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 b0cde9549a48d0d794995c18ce5e69e0de0c66a6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 14:25:52 +0100 Subject: [PATCH 155/583] Wrapped the regular expressions in "^" and "$" (to indicate that really a RegExp follows). Former-commit-id: ab965864c7444cc78c1bad77ec6f5341115be9fd --- .../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 d6f38a36b6d0ee62eccd18120cc62e17b2854631 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 16:58:41 +0100 Subject: [PATCH 156/583] Added test cases where methods are called that are out of scope and thus not interpreted. Former-commit-id: b5956ba4762ce6269cda53273bd709bf3238b2c6 --- .../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 09fd45cbd7bf33ec1c750777d0ca82f53965cd08 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 19:32:55 +0100 Subject: [PATCH 157/583] Formatted the file + fixed a typo. Former-commit-id: e68f71dda584cbda439ebff526860d43df1aeff8 --- .../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 ba3f975a61627eb9ac0517159a7cb56032fb8f6e Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 19:33:56 +0100 Subject: [PATCH 158/583] Extended a function call. Former-commit-id: 59bea78e3b750883f4feb95177e35c833f73aa23 --- .../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 aff85c545d6fb19440dafaf69564907c6c88a28a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 08:51:30 +0100 Subject: [PATCH 159/583] Removed ToDos (I guess they cannot be done as I thought). Former-commit-id: 28770fcd00de9b827f98d737d14603f3505bd522 --- .../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 1ec51cd250526f4d45ae543415c443f30838c028 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 08:53:46 +0100 Subject: [PATCH 160/583] Extended the interprocedural analysis to better handle float / double values. Former-commit-id: d8ca3cdd42eb9b5bdbc518b37c5dc86608eb9c1d --- .../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 20944757c57fbb3a4ebd77470a7b51938afb782a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 09:24:47 +0100 Subject: [PATCH 161/583] Added a comment describing the high-level approach of this analysis. Former-commit-id: 113968dcf1f82e9bc4fc8939b702903565e022a8 --- .../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 1c8ce11ab9a2f5d0710d0d5be66d36a89f85839c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 09:28:27 +0100 Subject: [PATCH 162/583] Added a comment describing the high-level approach of this analysis. Former-commit-id: d44fb1c263377c3c247dd7af991b4fe7f0a26f8f --- .../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 a57dd96cf7aee2f6ea1c26e6368bede1fb304e2f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 21:08:39 +0100 Subject: [PATCH 163/583] Extended the interprocedural analysis to finalize results of expressions where sub results have to be computed first. Corresponding test cases (initFromNonVirtualFunctionCallTest, arrayTest) were added. Former-commit-id: a13b87b2bf6841309a0c2749b582af4fe0c59e84 --- .../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 a03aa951cfc5af9035daae31612260b9d9f81f90 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 09:19:04 +0100 Subject: [PATCH 164/583] Improved the structure of the interpretation part by creating corresponding packages. Former-commit-id: eaf689c38e06a7801a071e47626fad7e3640e791 --- .../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 2ad88083080162cf406877dd1f889b029856939f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:15:07 +0100 Subject: [PATCH 165/583] Refactored code to avoid code duplication. Former-commit-id: 7ce9e6fee09b56ce6e8ba939de0a170dbe3daf61 --- .../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 e21b44b5630cf1f3bda581623b465a30ffd3266f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:35:18 +0100 Subject: [PATCH 166/583] Created a common interface for the finalizers. Former-commit-id: bab8ded854130db828910bf765fb95669ddaef62 --- .../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 22a110b8a1591214c781d4e956a38339b242b6d3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:37:24 +0100 Subject: [PATCH 167/583] Moved the "finalizer" package into the "interprocedural" package (as finalizers are only relevant for interprocedural processing). Former-commit-id: 479f648047cf75d023cd7caa084b3e33da161b3f --- .../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 d880ffe907c34ed2ea4b84c9ea413a82877110d8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 17:32:00 +0100 Subject: [PATCH 168/583] Interprocedural "append" operations are now supported. Former-commit-id: edafe1cff6e9594366559ecbf282bae68683f348 --- .../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 73ff59522694ac7397db78ac085b16dcb3c10e94 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Feb 2019 09:11:17 +0100 Subject: [PATCH 169/583] Removed a comment (TODO). Former-commit-id: aa57466eab00d43a8ed6536d1fdefc2bfff35164 --- .../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 bb0baa8101c2c9bb59571693cccc0a96d5ae5e2c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Feb 2019 09:13:55 +0100 Subject: [PATCH 170/583] Refined the "getDeclaredMethod" to include the declaring class in the search for a method as well. Former-commit-id: d2870c6f30fe387f0d0010146571282298890937 --- .../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 8b1f25c5a1a93a080a05a0766090cfcf6b8faec7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Feb 2019 14:18:49 +0100 Subject: [PATCH 171/583] Added support for virtual function calls where multiple implementations might be present. Corresponding test cases were added. Former-commit-id: 3def10af50f7e171b880400ce703edb663300457 --- .../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 711fe4e1dba6265515d58efe9b9d0af985ae4bef Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Feb 2019 14:22:06 +0100 Subject: [PATCH 172/583] As one parameter of StringConstancyInformation#reduceMultiple was changed to iterable, some calls to that function could be slightly simplified. Former-commit-id: 4551acf5a0a370a3da9288da97347fbd3715a9e0 --- .../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 42262f0e5d46ec4c76dd8c764d6101d48631f670 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Feb 2019 17:59:40 +0100 Subject: [PATCH 173/583] 1) Changed the InterproceduralStringAnalysis to work context-insensitive 2) The InterproceduralStringAnalysis now retrieves the TAC via the property store Former-commit-id: fd565161d0a33824dd31518c58becc2647eac9fd --- .../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 75aca07f2ba76ee58caf760f08f563c8ef817fb4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 15:47:59 +0100 Subject: [PATCH 174/583] 1) Fixed the usage of EPS handling 2) Minor changes on the ComputationState class Former-commit-id: 7710472ca896a7296438f300b959a3205d7ac18c --- .../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 ed67613bc7b878baf124becb5a5070a696f1670e Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:03:23 +0100 Subject: [PATCH 175/583] Added the computation state of the InterproceduralStringAnalysis into its own file. Former-commit-id: b74045a9eb218916b7824523fc137bcd78e59ead --- .../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 538cb775c8f9072ebb8ad897d0b57d4c5d1c0ba8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:08:53 +0100 Subject: [PATCH 176/583] Removed unnecessary arguments ("data: P"). Former-commit-id: b52502a2e96145410e48b9ecceddb6a45c628a3c --- .../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 00b0cc6d1b345e74d13932900b7c7486b9fee3d5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:35:03 +0100 Subject: [PATCH 177/583] Changed the "dependees" field in InterproceduralComputationState to a list. Former-commit-id: aa0eee9864b6b7d798e5206f6a4bf5ce4e99ef80 --- .../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 c3e9d27a01c04915515284164fd5914ed15a14bd Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 14:55:27 +0100 Subject: [PATCH 178/583] Extended the analysis in the way that it evaluates parameters of the method under analysis and uses these string information later on. Former-commit-id: df7a3afe586822a49ff1701140b1d68537d57c44 --- .../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 6647d22c45785596b2a1149441319550e6feb4ef Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 15:13:58 +0100 Subject: [PATCH 179/583] Removed unnecessary arguments / parameters. Former-commit-id: c055d864f80e705a6e7bf60be0e7e38d8b24b61b --- .../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 206543be385f6a04c7c172159c4e2127f5277ee8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:09 +0100 Subject: [PATCH 180/583] Fixed a little bug in the procedure that computes lean paths. Former-commit-id: 834b3f82b205026cd52b4e3fb50620d9d3e778b4 --- .../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 bcbf4fe3aad107369cfd61d45bec819c9f08d092 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:42 +0100 Subject: [PATCH 181/583] Fixed a little bug in the procedure that builds paths for "switch" control structures. Former-commit-id: 49b4f683da23319485653eff7203b0d9dbd76a46 --- .../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 c87dc62b0519d5f4e79804fd90be92e334169561 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:59 +0100 Subject: [PATCH 182/583] Removed a TODO comment. Former-commit-id: c308224ddb5a97e0cd70762c482d16cca533dbd9 --- .../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 465d8262a9da40638b77647fa0e9605ac583cf0c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:42:01 +0100 Subject: [PATCH 183/583] Allow the repetitive evaluation of constant expressions. Former-commit-id: 05ca8c5081d900ec8193e3c50ce294c4898957c5 --- .../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 83ba6f345d1558b4de66bcec6d419b4a4b31d481 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 19:19:20 +0100 Subject: [PATCH 184/583] Slightly changed the "isStringBuilderBufferToStringCall" function (it did not work when a class was not available, i.e., ClassNotFoundException). Former-commit-id: 7f735d83d1ef9ca3880cc4f984217450c1d0c796 --- .../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 c498a7a24f7c87a1b6e09fca06ac3750b78993c8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 19:36:49 +0100 Subject: [PATCH 185/583] Extended the ArrayPreparationInterpreter to capture a result in case it can be computed. Former-commit-id: 95611d68025d00078df7392ae97310d2aaa1291c --- .../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 595037ec3fd570ad3926f542056c92d713198abd Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 20 Feb 2019 17:31:06 +0100 Subject: [PATCH 186/583] Slightly changed the parameter registration and fixed a bug in the registerParams method. Former-commit-id: 694762cba1cdf4d219631b78526763eeea751d43 --- .../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 a587aafcf583e87fb2bc6c1819b885875e1cb6a8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 22 Feb 2019 17:15:22 +0100 Subject: [PATCH 187/583] Added primitive support for GetStatic expressions (needs to be enhanced later on). Former-commit-id: 63ba325ee123b73c61677fc76be66894ab37fe6c --- .../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 59d480ca7766e6e534598b6a29ea0e4638e0b06c Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 22 Feb 2019 17:15:48 +0100 Subject: [PATCH 188/583] Correctly handle the case if no TAC is available. Former-commit-id: 9bb468ab23290ab62b6bce5f44cfa09dc3e6c425 --- .../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 055e017f4ee3bc86e2de0c59b9e64069784809d4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 23 Feb 2019 14:26:22 +0100 Subject: [PATCH 189/583] Minor changes. Former-commit-id: d4498445ac676f6faac45f2d50e14af33bac83b3 --- .../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 a2136ff4825a0b1af471068a33f3ffc76607266c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 11:15:07 +0100 Subject: [PATCH 190/583] Added handling for the implicit "this" parameter. Former-commit-id: e9b0841f72f48b0137d1d2b42b0259620a060614 --- .../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 4411e35a45adf80fb2f3439ead2bf80731abbba1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 11:45:41 +0100 Subject: [PATCH 191/583] Added support for an "append" argument which > 1 definition site. Former-commit-id: e4635946e84ca5a9541dcc62ba6185ca3881ce07 --- .../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 d89e8f065c9a7c9e146834b2b7f3c3cf40d0c71f Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 17:20:13 +0100 Subject: [PATCH 192/583] Added support for functions as arguments to (other) functions. Former-commit-id: 51c1a99ca5e45d9d058cf66b82adbaf63f8b1449 --- .../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 e6700b28a35b95a79ef6b22c916df9f85485e9dd Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 19:47:25 +0100 Subject: [PATCH 193/583] Provide a default value if a path could only be reduced to the neutral element. Former-commit-id: c9986f1b69a1188fde1be24a7008afc840f717d2 --- .../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 6243d3777593d9ab86e930d42fcd66e8a1c6cfac Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 20:25:39 +0100 Subject: [PATCH 194/583] The "append" finalizer did not correctly finalize when multiple definition sites of the "append" parameter were present. Former-commit-id: 4fb2d8181ddb57851849cc8bf9d39b3b49f75a48 --- .../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 ff39265390c7bb53aaa7282cce14c27eaf856c9c Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 08:00:46 +0100 Subject: [PATCH 195/583] Finalizers might have dependencies among each other as well. These are now regarded (and a test case was added). Former-commit-id: d5d15f9e2554ce49abcb4047c84fcfe64effff09 --- .../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 fe6cba04d7aa1f50d8f2960f7d276cea91e355aa Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 11:54:30 +0100 Subject: [PATCH 196/583] Refined the procedure for finding the correct method for which to get callers information for. Former-commit-id: e0213d9361a5a08d4f787b56e0b49fd44dfc0af9 --- .../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 1a93c61fc879622e0158c570cc488289409976b0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 16:25:40 +0100 Subject: [PATCH 197/583] Converted some comments to doc strings. Former-commit-id: c59cbbe3d3d5f4fc1b361eea33b482d67b9c8302 --- .../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 8b512be18de51cdf7dae2066cdc439ff2b4a470e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 22:09:55 +0100 Subject: [PATCH 198/583] Added support for the interpretation of functions which have other functions as parameters. Former-commit-id: 910ee4103d388be8307aad4786a3b34496ac1171 --- .../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 d006d99fb3ffeeea2a6129a40159786bdd4ed98c Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 08:45:25 +0100 Subject: [PATCH 199/583] Fixed a little bug. Former-commit-id: e552189ee94ff25c84eb1cd2e9a75334fadaa9f4 --- .../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 454859d7847e6f4308e1c44891e8ee78f5479e56 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 09:42:13 +0100 Subject: [PATCH 200/583] Having the TAC and the CFG stored together is redundant => CFG was removed. Former-commit-id: fed2e565ab088ebe67c769b11f4a5808e4def7fa --- .../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 4408ad353f010a9bd58d15da4f4f5c054471f754 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 11:29:06 +0100 Subject: [PATCH 201/583] Added further comments / explanations regarding the feature implemented by commit #8da0654. Former-commit-id: 3071c290e5ded0447daedeea769b9c12f061a634 --- .../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 33f1547602b79893128c9bc559b0604c2084adab Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 11:38:36 +0100 Subject: [PATCH 202/583] Turned InterpretationHandler#processedDefSites into a map to have constant look-up times. Former-commit-id: 61edc77f1ccdb6d914040f4dcf5da60fdbb03654 --- .../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 317805298f9c505fa6de84ba20240d1d80273599 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 19:48:03 +0100 Subject: [PATCH 203/583] 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. Former-commit-id: d9ebb87978ec28a8d8e6a1c5c0ea6f6f7f6cccc0 --- .../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 22987855ff47cf7cd01ff88dbd68c623b0573246 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 19:49:04 +0100 Subject: [PATCH 204/583] Exception information are regarded as dynamic, i.e., lower bound. Former-commit-id: 0e5e5ab6553aa3067e8b6ef95b4574785a812724 --- .../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 26aac86e936188e4a5e3fb7db637cde61f46f6fa Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 12:48:01 +0100 Subject: [PATCH 205/583] Callers information are only collected if at least one of the parameters is of a supported type. Former-commit-id: be47bb2f4c04d0fc67245795a00a81af0c46b98b --- .../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 dacdc47f83d6837e825da4f329da810ac0dc2cdc Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 17:12:48 +0100 Subject: [PATCH 206/583] Improved handling of declared methods. Former-commit-id: ced527372aba2c5bd7f5e9f26b162590e461af0b --- .../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 61089a04cc59883a4cec1b82269441b723c9b7b9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 20:12:19 +0100 Subject: [PATCH 207/583] Simplified the condition whether to retrieve callers information or not. Former-commit-id: 323d1ff8af24bd00943e18dc5bc4b870107f9453 --- .../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 c3d8e4c4582a545b5e97ec6b732234038b8f2693 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Mar 2019 17:02:44 +0100 Subject: [PATCH 208/583] The path transformer may need to update the fpe2sci map. Former-commit-id: f65d2bc8cc0325ad284646f25ed4350ac063d478 --- .../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 ffa49dc8a22730cc72be4a50b7d2464c799989a8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Mar 2019 09:05:02 +0100 Subject: [PATCH 209/583] Refined the handling of functions. Former-commit-id: 859e254358cbff9526472bcc3bf70719617ac577 --- .../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 371bd642813ca76b9e295caa138839c9258bb0fb Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Mar 2019 19:20:18 +0100 Subject: [PATCH 210/583] Refined the procedure to determine whether callers information are required. Former-commit-id: b2783ad6cd7f84689d0fc93377dca29881719d73 --- .../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 4b34424b887e01f4a6b8f5ad716b0b2a4eb2aa76 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 12:07:07 +0100 Subject: [PATCH 211/583] In some cases it was unnecessary to collect callers information as they were already present. Former-commit-id: e76a5c4f1c70f4634bbea6bcb9e0afdb2d561a28 --- .../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 f41ad3a03b6db7078c1fc24d5774ed6362bfc896 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 15:08:07 +0100 Subject: [PATCH 212/583] Added handling for the default case. Former-commit-id: cc171816590f0b332677238bf3bcfe83a54eb75c --- .../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 ef5ff54c735aadc3a9c610093b0137693421630c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 19:51:10 +0100 Subject: [PATCH 213/583] Refined the finalization handling. Former-commit-id: df0face5da5515e2c2d9c1640dc4918008dad012 --- ...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 36f80889949cbcad03661c8b2bc27f3120236ee9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 22:15:08 +0100 Subject: [PATCH 214/583] Added a test case which, currently, does not work, as there is a cyclic dependency. Former-commit-id: 27ae9b0cbe1b99536c2a1e8ea05a339b41753950 --- .../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 9b53cd95ac83e8f6973c6a61597cb602876a8035 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 22:15:56 +0100 Subject: [PATCH 215/583] Refined the handling of NonVirtualFunctionCalls and modified some comments. Former-commit-id: 8907bfc7abe9a34682666f44b133c6823da47ef3 --- .../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 8300a9a01a94804915ff6c5ccfecf6ac90fdf435 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 09:03:59 +0100 Subject: [PATCH 216/583] Refined a condition. Former-commit-id: a6faf396f79edab01acab83472c252230a903ee7 --- .../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 97327963b0a011fe3b94414c3dc72beef4bf0552 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 09:19:11 +0100 Subject: [PATCH 217/583] Changed a parameter the "findPaths" function is called with. Former-commit-id: eaafcedad7ea6222346a25d9addaff396cdb9787 --- .../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 e06be23d2c306f7bfbe14a89aeee2736d6b6b40d Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:01:54 +0100 Subject: [PATCH 218/583] Modified a condition. Former-commit-id: b5cb02e6b45269bdfdc3356baf96e150fbeaa3aa --- .../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 32d93fffbf1bc19337876552b4a4ea36f8bf8cdb Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:04:10 +0100 Subject: [PATCH 219/583] Had to refine when a result is added to the fpe2sci map (adding the neutral element led to false results). Former-commit-id: 6320048736c32ae1e9b8030321fb180d48a6b506 --- .../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 786352092f5de3434c0103e573d8003faf4bf5fd Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:04:31 +0100 Subject: [PATCH 220/583] Added a test case which did not work previously but does so now because of the last commits. Former-commit-id: 674be0fa69c9ca8b4a1c6b7b7cddfb9d22a5cc38 --- .../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 1769cec97d5fc48da055994619e5223b81e52abe Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:31:52 +0100 Subject: [PATCH 221/583] Slightly refined the array finalizer in the way to finalize required data before its own finalization. Former-commit-id: d4ef7c114cea4a91720975c72376576704e18c4e --- .../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 a8cba15679df24fb7856bbd8694b11fb3f748643 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:20:04 +0100 Subject: [PATCH 222/583] Changed the handling when no TAC is available. Former-commit-id: a3b5a49dad25dd974decb14ded5ad884a8eadc68 --- .../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 b8966f03623314308c6e2adcea801704b3f5c581 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:20:33 +0100 Subject: [PATCH 223/583] Prevented the procedure from trying to elements that do not exist. Former-commit-id: 4bf280ea5644bfab545861551c9057cf6a12f405 --- .../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 a17a3c8a974a36e935212de2b6227978761fa5e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:59:27 +0100 Subject: [PATCH 224/583] Needed to add a case in order for the match to be exhaustive. Former-commit-id: 995661ed3c5d2e17275791d2a3f7dec2e5256e33 --- .../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 f15a2690e02f2543354297d38de1b33ed6a089b8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 15:53:38 +0100 Subject: [PATCH 225/583] Not only VirtualFunctionCalls might be involved in finding definition sites of an init => extended the support Former-commit-id: 504a83883f5b3e624f2f5d7a00d27cdb92a463c1 --- .../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 975f01d192d96707a7ca5a8c01b5a7d95f3b97f6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 15:55:58 +0100 Subject: [PATCH 226/583] 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. Former-commit-id: e4384318ade19c45b8a132647fda03a9ea0cc3b3 --- .../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 fa5ffd2df823ee4a287e19f30f780679247f236e Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 19:45:23 +0100 Subject: [PATCH 227/583] Changed the analysis to retrieve the TAC from the property store. Former-commit-id: eb506cdb793a59035c6fad5e30df9cffa243ba6d --- .../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 0cb93e604e3410c967c0d71b0700704becd653a8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:30:05 +0100 Subject: [PATCH 228/583] Started adding support for fields. Former-commit-id: a203c2bfbd9c6cc238f284b193a0a4121f6c0d64 --- .../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 9fa98e68eceda7aa09520bfd0bc998c1c5cb41e1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:58:31 +0100 Subject: [PATCH 229/583] Refined the handling of fields. Former-commit-id: 0aa4892b0de6267f1f663ea26e9a2e79ae8dcb63 --- .../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 785353ee57ffec49580dc9770a9ef169b278f665 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:58:49 +0100 Subject: [PATCH 230/583] Added some important details to a test case. Former-commit-id: 78c57a0ec5e5fe10fe6122d46f99fa4f9ea6e9a0 --- .../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 dfd917bbac36f51551486bd777bfa66a21a6e780 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 15:01:47 +0100 Subject: [PATCH 231/583] Added a test case where a field is read that is not written at all. Former-commit-id: 5e654e97b8680a31d94add4ec45f100016510baf --- .../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 b65f6bbfcd49b0674a23391f86a79977428a5b3b Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 16:40:06 +0100 Subject: [PATCH 232/583] Fixed a bug in the natural loop finding procedure. Sometimes, not entire loops were captured but only parts. Former-commit-id: b042b656699e37f3e02a55e40ba7cd3072433342 --- 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 103ed4ca21..5a7944cfb1 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 @@ -794,9 +794,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 8d344710e5954f59afcb4e45cc0911874fc5b5e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:26:04 +0100 Subject: [PATCH 233/583] The "fix" done with commit #729bee2 was not entirely correct (but now!). Former-commit-id: 5d5897b6b27ca82bc88224739dcfcbc5a57f839b --- 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 5a7944cfb1..562dde55e6 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 @@ -793,10 +793,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 71023c160834aec02f1519861e8a43d179f14d6e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:26:43 +0100 Subject: [PATCH 234/583] Formatted the file. Former-commit-id: b3acb16fd93dd7b32e05eccead0fc840e1254746 --- .../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 a2f6a0a67e2322e781c9ea8fa111f478140c6d36 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:35:22 +0100 Subject: [PATCH 235/583] Refined the getStartAndEndIndexOfCondWithAlternative procedure. Former-commit-id: b1f463a6e4f455cf176f4888df8ff20b8b32fe22 --- .../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 86292a9ef65138278eeb53fe435e3bf8590699ed Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 22:19:39 +0100 Subject: [PATCH 236/583] Added a condition to detect conditionals without alternative. Former-commit-id: 6e5d639c9e91359d371e8b1a8829d5b6d3530241 --- .../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 b97be2747c7a57094c53314827fda2519cebf629 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 07:32:06 +0100 Subject: [PATCH 237/583] Refined the handling of switch. Former-commit-id: f5d54d9746949ef250e89083603a314dba627765 --- .../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 65c8bbd133ab8363b68e1656af7d73f669f06054 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 08:00:20 +0100 Subject: [PATCH 238/583] Refined the handling of int/char values. Former-commit-id: c66a42bffef286cc8144810998f1fbfd7c4892ef --- ...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 05f1fdd6f627bbe79ec4330066f1ddd209a955aa Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 09:20:18 +0100 Subject: [PATCH 239/583] Refined the handling of switch. Former-commit-id: 469ddde8d248faa497c217fe8ca5b2b66f499f8f --- .../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 8d35ade8e53e9f2542344b131d932c71751d5a3a Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 12:19:36 +0100 Subject: [PATCH 240/583] 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). Former-commit-id: b7f4d66f6e5cdf4f44024fa2095171000e758068 --- .../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 97a158971d3f91dd5983158d807f954ba3f43a68 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 10:36:42 +0100 Subject: [PATCH 241/583] Had to change how the property store is setup in InterproceduralStringAnalysisTest. For the reson, see the class comment. Former-commit-id: 58fa1a68d0a4f2c1ed7ce4540ea1b4b2835b858b --- .../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 d2b3a8b7eac2a406813c1a806568011c714d045c Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 10:44:13 +0100 Subject: [PATCH 242/583] Slightly simplified the ArrayPreparationInterpreter. Former-commit-id: c632efec1d30ccd627a86b6c347ea59a2bb28925 --- .../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 64a6592dce414f3726ceb455e8d01cd4faa8c156 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:13:14 +0100 Subject: [PATCH 243/583] Formatted the file. Former-commit-id: 8e63412bcb3c0f66bfc2d57f6e6f0c035836e7d2 --- .../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 a2cc90462e494a5fcf926c171396ff11678412a3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:15:32 +0100 Subject: [PATCH 244/583] Refined the handling of the TAC. Former-commit-id: ee4731d6a3d67d28e66856f5a928974751781e1e --- .../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 622bbd0772bd1d68a69a2f7cff6ad7f2ac40d58d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:19:20 +0100 Subject: [PATCH 245/583] Restructured the file helper methods. Former-commit-id: 10de755078c7bcad7e73993b412ff93d00657f47 --- .../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 48c344e6b7215dfe847dca61044407a8a97a6b90 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 13:25:14 +0100 Subject: [PATCH 246/583] Simplified a routine. Former-commit-id: cfa04eaeb5a06c1b5c44bd2392256e4f1670b157 --- .../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 8fd10aa11624dc8895be06032acbd15e5597eff5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 15:32:13 +0100 Subject: [PATCH 247/583] Put an EPS to the dependees list only if a non-final result could be determined. Former-commit-id: e6873e4547d67ce7a8115b9da809c60003e73d73 --- .../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 b27cd8a85ed40878390ce37185efe54e21f7dfda Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 16:14:37 +0100 Subject: [PATCH 248/583] Do not add TAC entities twice to the property store. Former-commit-id: e880c30760d9583f401960f32a9a02c8481499ea --- .../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 ce30cd2420d21eea996aee85a83026ee33bae44b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 17:10:39 +0100 Subject: [PATCH 249/583] Avoided code duplication. Former-commit-id: dfbd010ae038a61087240c4b86e77d4b28fa0495 --- .../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 bfcddd25a64cadd4e88107b99b8d88d245c222f1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 19:27:57 +0100 Subject: [PATCH 250/583] Avoided some code duplication + cyclic dependencies are now resolved. Former-commit-id: cdf596558231aeaaaca47957044deb5c882264c0 --- .../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 a2abf3935945cd741b13e971d249653011b3ae68 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 20:56:49 +0100 Subject: [PATCH 251/583] Updated a comment. Former-commit-id: ed2e610987a1ba730b02f668ff047bcd9b9efdfa --- .../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 8857d1995e57f950bbf7664df759e20ab1ed6083 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 20:57:28 +0100 Subject: [PATCH 252/583] Refined the handling of field read operations. Former-commit-id: 0bed7024c488096b32ea691851429b77c2a5f9f7 --- .../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 c25218b0c28d8a89aef9b7ce18e2a6394d5aedae Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 21:09:30 +0100 Subject: [PATCH 253/583] Added a test case where a field of an unsupported type is read. Former-commit-id: db2a1d741754bddbe75015407d2734b32e99056b --- .../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 42327722673de2ae8e10878e134205c03326fe86 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 21:32:01 +0100 Subject: [PATCH 254/583] 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. Former-commit-id: 6842302a2279ff3c7e033af5677afb4cc4d2f1db --- .../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 98a98b708c74a59f32fd348d33c9730e6632346d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 22:26:19 +0100 Subject: [PATCH 255/583] Extended the support for primitive number types. Former-commit-id: aaa544de0523e0b9c3810dc8a5699f25379d7dba --- .../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 b29a823540f16bce8a9e7f17c3e043df56d44337 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 22:28:25 +0100 Subject: [PATCH 256/583] Added a test case where a field is initialized using a constructor parameter. Former-commit-id: cb77c5cf9bc3cac9c65a76ff7b62f092a50d4af5 --- .../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 15b5ea04ccce624b9126dfcc9dca971ea659c7fd Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 10:52:28 +0100 Subject: [PATCH 257/583] Slightly refined the procedure to detect conditionals with or without alternatives. Former-commit-id: c1f6c7cf0530513ca60385eef094c79c2a247463 --- .../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 492a39974ffcab7ad37e6e58d0428841a8fba25a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 17:40:12 +0100 Subject: [PATCH 258/583] Some (minor) improvements / bug fixes on the path finding procedure. Former-commit-id: 1057d88d7750f4234719c6472e2d4abbb7dabafc --- .../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 5ea04b5f5055ccb33c2927f1f2c6c2ef56c6e7ea Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 20:46:21 +0100 Subject: [PATCH 259/583] Some (minor) improvements / bug fixes on the path finding procedure. Former-commit-id: b8babeea108b463583d842905405c2b7a9d4658d --- .../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 ea80e8203102eccea041dca51c4c3a76aac8b9a3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 10:36:21 +0100 Subject: [PATCH 260/583] Made the path finding procedure more robust. Former-commit-id: 90d645051e6b681cd28dddf7c2ded9aae2df86d2 --- .../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 a527073c2ffd87815e55e6356c07c70d7aa6cd04 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 14:27:04 +0100 Subject: [PATCH 261/583] Refined the handling of the TACMade the path finding procedure more robust. Former-commit-id: a93254de77a66494f3d2cd23acb96f64a3a02307 --- .../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 cf020d0ace7ae3a1935c8e5788f99019f1bcbf73 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 14:27:24 +0100 Subject: [PATCH 262/583] Fixed a TODO regarding housekeeping. Former-commit-id: dfc9cc34c99da388f2d9938962d0289ff9653f13 --- .../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 d633fb664f9aeeb8a6cff7bb0c3490f01c1417c8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 17:09:58 +0100 Subject: [PATCH 263/583] Do not compare by reference. Former-commit-id: 24d6f02525ff9df8864b05cfb9c08a793879cf7d --- .../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 2787a3fe90318d1e8ce4689eb21b6971e809fb73 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 19:40:38 +0100 Subject: [PATCH 264/583] Added support for correctly handling intermediate results. Former-commit-id: 68853f6dd596434cf5c444e49afed76a1b7da7e9 --- .../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 7407456bdf10a1435cdc8a9e280f9be2f8658df7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 22:07:37 +0100 Subject: [PATCH 265/583] Avoided code duplication and cleaned the code. Former-commit-id: d1237ce5d5153337133259435e73a75296b5292e --- ...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 6d68260e874202d2c8eb4149a25cd507f498d1a9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 07:26:37 +0100 Subject: [PATCH 266/583] Added support for non-virtual functions which return more than one value. Former-commit-id: 68d05a41c0e600da8dd7e00b73e4143adc9cdbef --- .../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 82ed2e37acc7bef39c7e650b689ddb237f71beb3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 07:57:15 +0100 Subject: [PATCH 267/583] Added support for static functions which return more than one value. Former-commit-id: 7fb266c0968bd132885b1a89e31af5041023466f --- .../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 632a2bf24b3627d29caa4b849140ab57cce01729 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:28:14 +0100 Subject: [PATCH 268/583] Added test cases where functions have no return values. Former-commit-id: f4a4e0d3ba4af1e78b6535237043e9de44042b0b --- .../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 5fedbac3c7d0f17da9c8ad02d3429b80722ecb81 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:35:06 +0100 Subject: [PATCH 269/583] Virtual function with several return values are now supported as well. Former-commit-id: b4f07cb776dc610ac6efcd7a80527e9775c8a143 --- ...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 d65efc91b1d768e1d7ac7e8ffd1404d8179bd723 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:45:52 +0100 Subject: [PATCH 270/583] Merged two test cases into one. Former-commit-id: 7f80fdc412a970ba45d9025e0ffda92fd6510c9e --- .../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 61f5cdec9b07cdadb17bc98cfbae4c4169539c0c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 10:12:21 +0100 Subject: [PATCH 271/583] Added support for calls to String#valueOf. Former-commit-id: dc54852cdc02669d6c5ff5e3b7eabeda377c4b9d --- .../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 f6cf182a243c2137aa1f9092c9dd782bf6aaa51e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 10:16:18 +0100 Subject: [PATCH 272/583] Avoided code duplication. Former-commit-id: a9e14febbedc0a7c53b44f5a8e424ddf7cfc3044 --- .../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 d9f4694976631e9036841a5c3fe327e448289d02 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 15:50:59 +0100 Subject: [PATCH 273/583] 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. Former-commit-id: 131c65513e7c8a6eab2d35ec6ca3c133820aeb4d --- .../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 abbcad962cd609f0a1cad3b06c7061c8bdd3ea33 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 16:29:52 +0100 Subject: [PATCH 274/583] Implemented the hashCode and equals method (without it the Property Store might not be able to reach quiescence). Former-commit-id: a88f6026e815f9d113fe1e32e5dd73ccbdf4755a --- .../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 9ecea69f432015625a1e119f416c8e65d2449ebf Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 19:07:27 +0100 Subject: [PATCH 275/583] Extended the handling of arrays in the way that an interpretation may not necessarily produce an EPK at all. Former-commit-id: cbec20c466d3060662c66b38967eea9f51318cbe --- .../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 10c607a656a0761904d09608fba037d426bf72d7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 19:40:46 +0100 Subject: [PATCH 276/583] Made the path transforming procedure more robust. Former-commit-id: fd603e720b972aa06fd11abbd6bf5d2c743bcb6d --- .../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 23e0ec5552d13c90427bec9882bd08922fa08ef5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 20:10:55 +0100 Subject: [PATCH 277/583] A "Cond" or "Or" tree may not necessarily have append elements. Former-commit-id: d43bec6f657a4ad29deaef6c5b2fbe660e917d8c --- .../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 d9e7d529c2b64ed22f58e9425849be6145f0e876 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 20:53:35 +0100 Subject: [PATCH 278/583] Slightly improved the procedure to determine whether a conditional has an "else" branch. Former-commit-id: 0986039b9d1b79647a2857eb7ffc58ab03240a6f --- .../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 0c73da30749c98563d744c5629be15a1674bc1c0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 22:32:50 +0100 Subject: [PATCH 279/583] On every generation of an interim, compute a new upper bound. Former-commit-id: af71efc7558989dc88dbdb38deacdef6c6ff6241 --- .../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 2169861053ab3eaa9828c9db53e2e5cbd86c3a35 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 22:54:36 +0100 Subject: [PATCH 280/583] Narrowed the return type of InterproceduralInterpretationHandler#processDefSite down to EOptionP[Entity, StringConstancyProperty]. Former-commit-id: 96cf15d89fa94358844f4f47a5fdb0081e4f9bb6 --- .../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 184816c5b0a6bb327950fe5fffb02e96762f5062 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 06:54:36 +0100 Subject: [PATCH 281/583] Removed the "continuation" parameter from the InterproceduralInterpretationHandler as it is no longer necessary / used. Former-commit-id: 0f0d1ef35280181677565475792ac135f00a89c5 --- .../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 80b302b597b4a15436a1f895ed7304b2043c1900 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 07:57:17 +0100 Subject: [PATCH 282/583] Had to refine the state of the interim state ("copy" does not copy all necesssary information). Former-commit-id: bd415e2fb1b1afee2c5bd20174a003719f1c61a2 --- .../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 c086b095851f335395d3899676b4cac3f5b41de9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 09:13:40 +0100 Subject: [PATCH 283/583] Refined the handling of try-catch-finally. Former-commit-id: 2e2ce0302be880443131d670b0159742c9a33776 --- .../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 af675a165818c186c03f6281ce430dfdb4ad42ce Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 09:22:22 +0100 Subject: [PATCH 284/583] Refined the procedure to find the definition sites of a StringBuilder. Former-commit-id: 5445bd2943ece77aba1a6d7b6d95957a4c3e1378 --- .../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 50f0662b6c30858ea94cfe3e33ed4c66077587fc Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 11:52:36 +0100 Subject: [PATCH 285/583] Avoid an endless loop by tracking seen elements. Former-commit-id: bc695c6b5cc5e506ead2bc7c6f1f7e74c3a24b94 --- .../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 468c4b0fd80f24a8795e4f3689b0606bba15f6d8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 11:53:10 +0100 Subject: [PATCH 286/583] Avoid endless recursion by checking to not start the procedure with itself. Former-commit-id: 0005265676c9fd1a02f38334a7d5ca76762272b0 --- .../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 4bcdf16799f2a6de14799290a49a32b66ccc176f Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 16:26:11 +0100 Subject: [PATCH 287/583] Refined the handling of callers by introducing a threshold. Former-commit-id: bc063d0b95361c11460329cad12890f53f5793ce --- .../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 fe915f616378f9275418a247ce5b5317d47c0a4b Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 16:26:43 +0100 Subject: [PATCH 288/583] Improved the handling of try-catch blocks. Former-commit-id: cad5b0e3c9584761b936294c67688fd2461bc0fa --- .../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 6e355a1676749db8e3852c85efbb2f4acfd151fd Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 18:57:33 +0100 Subject: [PATCH 289/583] Removed an unnecessary line. Former-commit-id: ffeebd3b7a6f5318c7964f1c3216a0f1b2ca58b7 --- .../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 54c1877e47538ae353edc17dc7317cb3978b50f6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 19:53:10 +0100 Subject: [PATCH 290/583] Improved the handling of interim results. Former-commit-id: c6ce04339312e89788b3a6930db7e9448c5e2d24 --- .../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 dbef560c4be1473070e6073154d3c438b0789edd Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 07:57:05 +0100 Subject: [PATCH 291/583] 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). Former-commit-id: fa421d0f9f334ddc1c5c3a154168f61e26a1a06d --- .../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 469784cacbee99b46a7a52921fb7956fbaca78eb Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 08:24:06 +0100 Subject: [PATCH 292/583] Added a question / todo. Former-commit-id: 6ab6e8f91fb7211012c83f0d12d439f6232879e1 --- .../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 781236c114e7eb909447659c88220c39f800f425 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 13:28:35 +0100 Subject: [PATCH 293/583] Made the procedure more robust. Former-commit-id: 14aae82263b106c2bc1afbc99acdbfcef7756bed --- .../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 a5b3ee630f207b1df47a0ab6e69e0bf70bd5e704 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 17:10:00 +0100 Subject: [PATCH 294/583] Committed the state of this analysis which is actually used to analyze the JDK for the evaluation. Former-commit-id: 0bc882840d0da0d8a0f6d17b911c802da97beb86 --- .../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 9153ae4b01b7d6afa8f9f716705e7e898f5eb3a5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 19:46:24 +0100 Subject: [PATCH 295/583] Added support for static field read operations. Former-commit-id: 41a6767b0bd0a43a99fcd82827a9c3d184dbcb4c --- .../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 00941d0ec0950dbb4829a1ecef1635e18446e316 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 14 Mar 2019 19:11:52 +0100 Subject: [PATCH 296/583] 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. Former-commit-id: 7b09d524538079f68a0ecc80ee97df067889983d --- .../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 a73bdabd2231e1fd34cf2e089cf5dd068d0cba7e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 14 Mar 2019 20:23:04 +0100 Subject: [PATCH 297/583] Fixed a typo. Former-commit-id: c20a279ac175cb8fdf04eb0546abb034431f1563 --- .../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 73e0ea56186e70301932ef773de048dd6f594402 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 09:25:13 +0100 Subject: [PATCH 298/583] The path finding procedure could run in an endless loop which is now prevented. Former-commit-id: de09255f00d342685fc497b4287dcd11f7d77d80 --- .../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 45877e59e019263783156a69b91d691aaca69919 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 09:40:23 +0100 Subject: [PATCH 299/583] Made this finalizer more robust. Former-commit-id: f4488c26e279671a83308dc02f69fad59e58d592 --- .../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 4d9a27014ae6d94001b984e46ee431255db09e6f Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 11:26:40 +0100 Subject: [PATCH 300/583] Made the path finding procedure more robust by avoiding -1 values if the end PC of a catch node is -1. Former-commit-id: 5fd710b70dc3f83551664c2548fc63cbf75e91f1 --- .../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 f7c20d3b57ce2413161f9ffc366cd83e4de23c12 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 11:48:24 +0100 Subject: [PATCH 301/583] Made the reduce accumulator more robust. Former-commit-id: f98da706e2a12a13c5d03304933b55d4cc17927c --- .../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 1257a683b863d0f7de75283b4beda7b5858b598b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 13:36:14 +0100 Subject: [PATCH 302/583] Made the path finding procedure more robust by avoiding that the end side can be smaller than the start side. Former-commit-id: cbf109dc41cc7c855f5e993fb808857451a3e48b --- .../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 a8fb8456d2caf44afbbaa26a7096a389b57297e2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 17 Mar 2019 19:37:22 +0100 Subject: [PATCH 303/583] 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. Former-commit-id: af955267ab627fd2f7659696b7e4f1343a43f0b6 --- .../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 82cf89c1851b38f0307adae8d981fe04a84aabbe Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 18 Mar 2019 16:18:37 +0100 Subject: [PATCH 304/583] Added a comment / TODO. Former-commit-id: 4c0dc00db31055451dc8feef164861d2f3459338 --- .../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 7ac23b772b6a5cc1453ff57f637ed357e8fcd455 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 18 Mar 2019 16:40:38 +0100 Subject: [PATCH 305/583] Added a comment / TODO. Former-commit-id: eebac09148a6796f0513af0b74898c53d9bf8f31 --- .../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 98cf3d768f1d9e642cbb3c9a6addcbcee01ab616 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 10:50:26 +0100 Subject: [PATCH 306/583] 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. Former-commit-id: fb0212f3fecd8b4d6d744a9aeea914a91196cb98 --- .../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 d818f6d455daf554b06e86d10ef66b4bdef586ce Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 11:27:44 +0100 Subject: [PATCH 307/583] Under some circumstances, the empty string was turned into the lower bound. This lead to one little refinement in the VirtualFunctionCallPreparationInterpreter. Former-commit-id: d3bef6a200ea955e388246dddcc15f6278a7f67d --- .../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 2ec06b073581f77f084b2d868cea4e2ee5b23b25 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 17:33:33 +0100 Subject: [PATCH 308/583] Added support for NewArray expressions. Former-commit-id: d8215e8ef5fd589a4f9ec5037c9739017f9f755a --- .../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 b1021f1516e12e50cd0c27cec1968e9c167fb688 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 25 Mar 2019 15:52:06 +0100 Subject: [PATCH 309/583] Approximate the results of a function, which does not return a String, with the lower bound. Former-commit-id: 2e34a6aad85c6d1ea7b14fb040d1591a101b868a --- .../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 2775500f6a8360ab2e8bf6b07b8ed5e5ca5a9e4b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 25 Mar 2019 16:07:33 +0100 Subject: [PATCH 310/583] Merge remote-tracking branch 'upstream/develop' into feature/string-analysis-new-develop Former-commit-id: 73a46d1b2cb902f300d34ec747086ea38b96210a --- .../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 24c72d19ce..5b697124b1 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 05e3a46903..7d01b77b1a 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 dded3d4ef4a6b4499091dc5824df98fde69674fa Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 2 Apr 2019 20:24:41 +0200 Subject: [PATCH 311/583] Changed the way the (interprocedural) string analysis tests are executed. Former-commit-id: 600d3a15ba51b9c0dc9ec79b8282887c05a4415e --- .../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 0c54c8d668b2ba120152c51d16b9031589d2a093 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 2 Apr 2019 20:25:30 +0200 Subject: [PATCH 312/583] Merge remote-tracking branch 'upstream/develop' into feature/string-analysis-new-develop Former-commit-id: 7cf36b53f54ccf5ab9837e79df25f14fc56c80e6 --- .../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 5b697124b1..633a79b91a 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 7d01b77b1a..d6979ddcbc 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 5417973d23d5a0c81831efad38624be8629578a0 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/583] re-applied review comments from PR #1 Former-commit-id: 614a9d487563eced85c8c307f4e569848382a417 --- .../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 87f52c97c0..47c7f7f38f 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 85f63c440d49c6026fd026dff25409c21a1760d1 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/583] sync with develop Former-commit-id: 944a63a40ccbe9846d42cd9495b57c1dd032130e --- ...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 0ce3f6bee2ac8564291f70737417df75b754e5fa 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/583] resolved merge conflicts Former-commit-id: 82e245bd0b26bee2c4e5a0a8789595020a742e5c --- .../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 | 100 ++++++++--------- 21 files changed, 96 insertions(+), 374 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 47c7f7f38f..13fc28dfeb 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 @@ -153,7 +153,7 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { ): ReportableAnalysisResult = { getAnalysisParameters(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 562dde55e6..f3a2c0654f 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 @@ -735,21 +735,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 633a79b91a..174ee0b890 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 d6979ddcbc..6cd6bbfc31 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 7db2d927dd..46b1279ebd 100644 --- a/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala +++ b/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala @@ -44,64 +44,64 @@ class TACAITest extends AnyFunSpec with Matchers { for { Seq(_, _, className, methodName, test, domainName) <- tests } { 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 a5ba313b883f5244c52b8ea61806e0c2cf9e4268 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/583] minor refactoring in tests Former-commit-id: ac288959c7d7e16c0031290ad08db631480bf23c --- .../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() From af384a3ac72015e71204dcc7bb38a5119789d419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Wed, 1 Feb 2023 13:46:54 +0100 Subject: [PATCH 317/583] Resolve conflicts. Upgrade PR code to OPAL 5.0.0, fix all resulting errors. Make Code build and Unit tests pass. No changes to new functionality yet. --- .../support/info/ClassUsageAnalysis.scala | 36 ++- .../info/StringAnalysisReflectiveCalls.scala | 60 ++--- .../org/opalj/fpcf/StringAnalysisTest.scala | 31 ++- .../StringAnalysisMatcher.scala | 14 +- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 22 +- .../properties/StringConstancyProperty.scala | 6 +- .../StringConstancyInformation.scala | 8 +- .../string_definition/StringTree.scala | 60 ++--- .../org/opalj/br/cfg/DominatorTreeTest.scala | 15 +- .../StringConstancyLevelTests.scala | 7 +- .../InterproceduralComputationState.scala | 9 +- .../InterproceduralStringAnalysis.scala | 173 ++++++------- .../IntraproceduralStringAnalysis.scala | 46 ++-- .../AbstractStringInterpreter.scala | 50 ++-- .../InterpretationHandler.scala | 46 ++-- .../common/BinaryExprInterpreter.scala | 6 +- .../interprocedural/ArrayLoadPreparer.scala | 16 +- .../InterproceduralFieldInterpreter.scala | 12 +- ...InterproceduralInterpretationHandler.scala | 91 +++---- ...ralNonVirtualFunctionCallInterpreter.scala | 8 +- ...duralNonVirtualMethodCallInterpreter.scala | 18 +- ...ceduralStaticFunctionCallInterpreter.scala | 31 +-- ...oceduralVirtualMethodCallInterpreter.scala | 6 +- .../interprocedural/NewArrayPreparer.scala | 8 +- ...alFunctionCallPreparationInterpreter.scala | 61 ++--- .../finalizer/ArrayLoadFinalizer.scala | 4 +- .../NonVirtualMethodCallFinalizer.scala | 2 +- .../StaticFunctionCallFinalizer.scala | 2 +- .../VirtualFunctionCallFinalizer.scala | 16 +- .../IntraproceduralArrayInterpreter.scala | 14 +- ...IntraproceduralInterpretationHandler.scala | 34 +-- ...duralNonVirtualMethodCallInterpreter.scala | 10 +- ...eduralVirtualFunctionCallInterpreter.scala | 24 +- ...oceduralVirtualMethodCallInterpreter.scala | 4 +- .../preprocessing/AbstractPathFinder.scala | 237 +++++++++--------- .../string_analysis/preprocessing/Path.scala | 78 +++--- .../preprocessing/PathTransformer.scala | 38 +-- .../preprocessing/WindowPathFinder.scala | 8 +- .../test/scala/org/opalj/tac/TACAITest.scala | 100 ++++---- 39 files changed, 708 insertions(+), 703 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 13fc28dfeb..6d28c7e5fd 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 @@ -2,14 +2,10 @@ 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.Project import org.opalj.br.analyses.ProjectAnalysisApplication @@ -47,7 +43,7 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { * The fully-qualified name of the class that is to be analyzed in a Java format, i.e., dots as * package / class separators. */ - private var className = "java.lang.StringBuilder" + private val className = "java.lang.StringBuilder" /** * The analysis can run in two modes: Fine-grained or coarse-grained. Fine-grained means that @@ -57,7 +53,7 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { * base object as well as the method name are equal, i.e., overloaded methods are not * distinguished. */ - private var isFineGrainedAnalysis = false + private val isFineGrainedAnalysis = false /** * Takes a [[Call]] and assembles the method descriptor for this call. The granularity is @@ -83,9 +79,11 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { 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) + val methodDescriptor = assembleMethodDescriptor(call) + if (map.contains(methodDescriptor)) { + map.put(methodDescriptor, 1) + } else { + map.update(methodDescriptor, map(methodDescriptor) + 1) } } } @@ -155,20 +153,20 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { val resultMap = mutable.Map[String, Int]() val tacProvider = project.get(EagerDetachedTACAIKey) - project.allMethodsWithBody.foreach { m ⇒ - tacProvider(m).stmts.foreach { stmt ⇒ + project.allMethodsWithBody.foreach { m => + tacProvider(m).stmts.foreach { stmt => (stmt.astID: @switch) match { - case Assignment.ASTID ⇒ stmt match { - case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ + case Assignment.ASTID => stmt match { + case Assignment(_, _, c: VirtualFunctionCall[V]) => processFunctionCall(c, resultMap) - case _ ⇒ + case _ => } - case ExprStmt.ASTID ⇒ stmt match { - case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ + case ExprStmt.ASTID => stmt match { + case ExprStmt(_, c: VirtualFunctionCall[V]) => processFunctionCall(c, resultMap) - case _ ⇒ + case _ => } - case _ ⇒ + case _ => } } } @@ -176,7 +174,7 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { 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" + case (descriptor: String, count: Int) => s"$descriptor: $count" }) BasicReport(report) } 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 4dfc5cb557..f3a6c5d402 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 @@ -136,15 +136,15 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * relevant method that is to be processed by `doAnalyze`. */ private def instructionsContainRelevantMethod(instructions: Array[Instruction]): Boolean = { - instructions.filter(_ != null).foldLeft(false) { (previous, nextInstr) ⇒ + instructions.filter(_ != null).foldLeft(false) { (previous, nextInstr) => previous || ((nextInstr.opcode: @switch) match { - case INVOKESTATIC.opcode ⇒ + case INVOKESTATIC.opcode => val INVOKESTATIC(declClass, _, methodName, _) = nextInstr isRelevantCall(declClass, methodName) - case INVOKEVIRTUAL.opcode ⇒ + case INVOKEVIRTUAL.opcode => val INVOKEVIRTUAL(declClass, methodName, _) = nextInstr isRelevantCall(declClass, methodName) - case _ ⇒ false + case _ => false }) } } @@ -166,7 +166,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { ) // Loop through all parameters and start the analysis for those that take a string call.descriptor.parameterTypes.zipWithIndex.foreach { - case (ft, index) ⇒ + case (ft, index) => if (ft.toJava == "java.lang.String") { val duvar = call.params(index).asVar val e = (duvar, method) @@ -188,13 +188,13 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { */ private def resultMapToReport(resultMap: ResultMapType): BasicReport = { val report = ListBuffer[String]("Results of the Reflection Analysis:") - for ((reflectiveCall, entries) ← resultMap) { + 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 + case StringConstancyLevel.CONSTANT => constantCount += 1 + case StringConstancyLevel.PARTIALLY_CONSTANT => partConstantCount += 1 + case StringConstancyLevel.DYNAMIC => dynamicCount += 1 } } @@ -212,24 +212,24 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { m: Method, resultMap: ResultMapType ): Unit = { - stmts.foreach { stmt ⇒ + 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]) ⇒ + case Assignment.ASTID => stmt match { + case Assignment(_, _, c: StaticFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ + case Assignment(_, _, c: VirtualFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case _ ⇒ + case _ => } - case ExprStmt.ASTID ⇒ stmt match { - case ExprStmt(_, c: StaticFunctionCall[V]) ⇒ + case ExprStmt.ASTID => stmt match { + case ExprStmt(_, c: StaticFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ + case ExprStmt(_, c: VirtualFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case _ ⇒ + case _ => } - case _ ⇒ + case _ => } } } @@ -238,19 +238,19 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { ps: PropertyStore, m: Method, resultMap: ResultMapType )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case FinalP(tac: TACAI) ⇒ + case FinalP(tac: TACAI) => processStatements(ps, tac.tac.get.stmts, m, resultMap) Result(m, tac) - case InterimLUBP(lb, ub) ⇒ + case InterimLUBP(lb, ub) => InterimResult( - m, lb, ub, List(eps), continuation(ps, m, resultMap) + m, lb, ub, Set(eps), continuation(ps, m, resultMap) ) - case _ ⇒ throw new IllegalStateException("should never happen!") + case _ => throw new IllegalStateException("should never happen!") } } override def doAnalyze( - project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean + project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) @@ -263,7 +263,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { val resultMap: ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } - project.allMethodsWithBody.foreach { m ⇒ + project.allMethodsWithBody.foreach { m => // To dramatically reduce work, quickly check if a method is relevant at all if (instructionsContainRelevantMethod(m.body.get.instructions)) { var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = null @@ -281,7 +281,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { m, StringConstancyProperty.ub, StringConstancyProperty.lb, - List(tacaiEOptP), + Set(tacaiEOptP), continuation(propertyStore, m, resultMap) ) } @@ -291,13 +291,13 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { val t0 = System.currentTimeMillis() propertyStore.waitOnPhaseCompletion() entityContext.foreach { - case (e, callName) ⇒ + case (e, callName) => propertyStore.properties(e).toIndexedSeq.foreach { - case FinalP(p: StringConstancyProperty) ⇒ + case FinalP(p: StringConstancyProperty) => resultMap(callName).append(p.stringConstancyInformation) - case InterimELUBP(_, _, ub: StringConstancyProperty) ⇒ + case InterimELUBP(_, _, ub: StringConstancyProperty) => resultMap(callName).append(ub.stringConstancyInformation) - case _ ⇒ + case _ => println(s"Neither a final nor an interim result for $e in $callName; "+ "this should never be the case!") } 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 b359494a68..e40fef479f 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 @@ -7,7 +7,6 @@ import java.net.URL import scala.collection.mutable.ListBuffer -import org.opalj.collection.immutable.Chain import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method @@ -48,7 +47,7 @@ sealed class StringAnalysisTestRunner( 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) } + necessaryFiles.map { filePath => new File(basePath + filePath) } } /** @@ -71,14 +70,14 @@ sealed class StringAnalysisTestRunner( val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( - (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) + (exists, a) => exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) ) - } foreach { m ⇒ + } foreach { m => StringAnalysisTestRunner.extractUVars( tacProvider(m).cfg, fqTestMethodsClass, nameTestMethod - ).foreach { uvar ⇒ - entitiesToAnalyze.append((uvar, m)) - } + ).foreach { uvar => + entitiesToAnalyze.append((uvar, m)) + } } entitiesToAnalyze } @@ -86,16 +85,16 @@ sealed class StringAnalysisTestRunner( 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 + ): Iterable[((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(project).filter(am ⇒ m2e.contains(am._1)).flatMap { am ⇒ + val eas = methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => m2e(am._1).zipWithIndex.map { - case (duvar, index) ⇒ + case (duvar, index) => Tuple3( (duvar, am._1), - { s: String ⇒ s"${am._2(s)} (#$index)" }, + { s: String => s"${am._2(s)} (#$index)" }, List(getStringDefinitionsFromCollection(am._3, index)) ) } @@ -135,9 +134,9 @@ object StringAnalysisTestRunner { nameTestMethod: String ): List[V] = { cfg.code.instructions.filter { - case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ + case VirtualMethodCall(_, declClass, _, name, _, _, _) => declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod - case _ ⇒ false + case _ => false }.map(_.asVirtualMethodCall.params.head.asVar).toList } @@ -164,7 +163,7 @@ class IntraproceduralStringAnalysisTest extends PropertiesTest { val entities = runner.determineEntitiesToAnalyze(p) val (_, analyses) = manager.runAll( List(LazyIntraproceduralStringAnalysis), - { _: Chain[ComputationSpecification[FPCFAnalysis]] ⇒ + { _: List[ComputationSpecification[FPCFAnalysis]] => entities.foreach(ps.force(_, StringConstancyProperty.key)) } ) @@ -219,7 +218,7 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { val (_, analyses) = manager.runAll( analysesToRun, - { _: Chain[ComputationSpecification[FPCFAnalysis]] ⇒ + { _: List[ComputationSpecification[FPCFAnalysis]] => entities.foreach(ps.force(_, StringConstancyProperty.key)) } ) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index aa5515d54f..faf7ef17d1 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -26,8 +26,8 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { */ private def getConstancyLevel(a: AnnotationLike): String = { a.elementValuePairs.find(_.name == "expectedLevel") match { - case Some(el) ⇒ el.value.asEnumValue.constName - case None ⇒ throw new IllegalArgumentException( + case Some(el) => el.value.asEnumValue.constName + case None => throw new IllegalArgumentException( "Can only extract the constancy level from a StringDefinitions annotation" ) } @@ -43,8 +43,8 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { */ private def getExpectedStrings(a: AnnotationLike): String = { a.elementValuePairs.find(_.name == "expectedStrings") match { - case Some(el) ⇒ el.value.asStringValue.value - case None ⇒ throw new IllegalArgumentException( + case Some(el) => el.value.asStringValue.value + case None => throw new IllegalArgumentException( "Can only extract the possible strings from a StringDefinitions annotation" ) } @@ -58,16 +58,16 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { as: Set[ObjectType], entity: Any, a: AnnotationLike, - properties: Traversable[Property] + properties: Iterable[Property] ): Option[String] = { var actLevel = "" var actString = "" properties.head match { - case prop: StringConstancyProperty ⇒ + case prop: StringConstancyProperty => val sci = prop.stringConstancyInformation actLevel = sci.constancyLevel.toString.toLowerCase actString = sci.possibleStrings - case _ ⇒ + case _ => } val expLevel = getConstancyLevel(a).toLowerCase 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 f3a2c0654f..b554c99d9c 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 @@ -9,7 +9,7 @@ import java.util.Arrays import org.opalj.collection.immutable.EmptyIntTrieSet -import scala.collection.{Set ⇒ SomeSet} +import scala.collection.{Set => SomeSet} import scala.collection.AbstractIterator import org.opalj.log.LogContext @@ -760,7 +760,7 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( // 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: _*) + val toVisitStack = mutable.Stack[Int](successors(start).toList: _*) // backedges stores all back-edges in the form (from, to) (where to dominates from) val backedges = ListBuffer[(Int, Int)]() while (toVisitStack.nonEmpty) { @@ -768,11 +768,11 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( val to = successors(from).toArray // Check for back-edges (exclude catch nodes here as this would detect loops where // no actually are - to.filter { next ⇒ + to.filter { next => val index = seenNodes.indexOf(next) val isCatchNode = catchNodes.exists(_.handlerPC == next) index > -1 && !isCatchNode && domTree.doesDominate(seenNodes(index), from) - }.foreach { 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 @@ -781,8 +781,8 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( val hasDest = backedges.exists(_._2 == destIndex) var removedBackedge = false backedges.filter { - case (oldTo: Int, oldFrom: Int) ⇒ oldFrom == destIndex && oldTo < from - }.foreach { toRemove ⇒ removedBackedge = true; backedges -= toRemove } + case (oldTo: Int, oldFrom: Int) => oldFrom == destIndex && oldTo < from + }.foreach { toRemove => removedBackedge = true; backedges -= toRemove } if (!hasDest || removedBackedge) { backedges.append((from, destIndex)) } @@ -793,7 +793,7 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( } // Finally, assemble the lists of loop elements - naturalLoops = Some(backedges.map { case (dest, root) ⇒ root.to(dest).toList }.toList) + naturalLoops = Some(backedges.map { case (dest, root) => root.to(dest).toList }.toList) } naturalLoops.get @@ -805,19 +805,19 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( * @see [[PostDominatorTree.apply]] */ def postDominatorTree: PostDominatorTree = { - val exitNodes = basicBlocks.zipWithIndex.filter { next ⇒ + 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), + 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)), + (f: Int => Unit) => exitNodes.foreach(e => f(e)), foreachSuccessor, foreachPredecessor, - basicBlocks.foldLeft(0) { (prevMaxNode: Int, next: BasicBlock) ⇒ + basicBlocks.foldLeft(0) { (prevMaxNode: Int, next: BasicBlock) => math.max(prevMaxNode, next.endPC) } ) 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 983a5c5adb..602d983f1f 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 @@ -37,9 +37,9 @@ class StringConstancyProperty( override def hashCode(): Int = stringConstancyInformation.hashCode() override def equals(o: Any): Boolean = o match { - case scp: StringConstancyProperty ⇒ + case scp: StringConstancyProperty => stringConstancyInformation.equals(scp.stringConstancyInformation) - case _ ⇒ false + case _ => false } } @@ -50,7 +50,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta final val key: PropertyKey[StringConstancyProperty] = { PropertyKey.create( PropertyKeyName, - (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { + (_: PropertyStore, _: FallbackReason, _: Entity) => { // TODO: Using simple heuristics, return a better value for some easy cases lb } 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 d481043094..b90ae8e2b7 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 @@ -75,10 +75,10 @@ object StringConstancyInformation { // 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 - case 0 ⇒ StringConstancyInformation.getNeutralElement - case 1 ⇒ relScis.head - case _ ⇒ // Reduce - val reduced = relScis.reduceLeft((o, n) ⇒ + case 0 => StringConstancyInformation.getNeutralElement + case 1 => relScis.head + case _ => // Reduce + val reduced = relScis.reduceLeft((o, n) => StringConstancyInformation( StringConstancyLevel.determineMoreGeneral( o.constancyLevel, n.constancyLevel 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 99e73e9f15..0015bad32d 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 @@ -29,7 +29,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme val replaceElement = reduced.find(_.constancyType == StringConstancyType.REPLACE) val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } val appendSci = if (appendElements.nonEmpty) { - Some(appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( + Some(appendElements.reduceLeft((o, n) => StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), StringConstancyType.APPEND, s"${o.possibleStrings}|${n.possibleStrings}" @@ -73,14 +73,14 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme // 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) + (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 ⇒ + 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) @@ -106,7 +106,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme } // Otherwise, collapse / combine with previous elements else { scis.zipWithIndex.foreach { - case (innerSci, index) ⇒ + case (innerSci, index) => val collapsed = StringConstancyInformation( StringConstancyLevel.determineForConcat( innerSci.constancyLevel, nextSci.constancyLevel @@ -137,7 +137,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme */ private def reduceAcc(subtree: StringTreeElement): List[StringConstancyInformation] = { subtree match { - case StringTreeRepetition(c, lowerBound, upperBound) ⇒ + case StringTreeRepetition(c, lowerBound, upperBound) => val times = if (lowerBound.isDefined && upperBound.isDefined) (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol val reducedAcc = reduceAcc(c) @@ -148,10 +148,10 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme reduced.constancyType, s"(${reduced.possibleStrings})$times" )) - 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) + 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) } } @@ -168,15 +168,15 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme val seen = mutable.Map[StringConstancyInformation, Boolean]() val unique = ListBuffer[StringTreeElement]() children.foreach { - case next @ StringTreeConst(sci) ⇒ + case next @ StringTreeConst(sci) => if (!seen.contains(sci)) { - seen += (sci → true) + seen += (sci -> true) unique.append(next) } - case loop: StringTreeRepetition ⇒ unique.append(loop) - case concat: StringTreeConcat ⇒ unique.append(concat) - case or: StringTreeOr ⇒ unique.append(or) - case cond: StringTreeCond ⇒ unique.append(cond) + 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 } @@ -186,23 +186,23 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme */ private def simplifyAcc(subtree: StringTree): StringTree = { subtree match { - case StringTreeOr(cs) ⇒ + case StringTreeOr(cs) => cs.foreach { - case nextC @ StringTreeOr(subChildren) ⇒ + case nextC @ StringTreeOr(subChildren) => simplifyAcc(nextC) var insertIndex = subtree.children.indexOf(nextC) - subChildren.foreach { next ⇒ + subChildren.foreach { next => subtree.children.insert(insertIndex, next) insertIndex += 1 } subtree.children.-=(nextC) - case _ ⇒ + case _ => } val unique = removeDuplicateTreeValues(cs) subtree.children.clear() subtree.children.appendAll(unique) subtree - case stc: StringTreeCond ⇒ + 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)*). @@ -212,9 +212,9 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme stc } // Remaining cases are trivial - case str: StringTreeRepetition ⇒ StringTreeRepetition(simplifyAcc(str.child)) - case stc: StringTreeConcat ⇒ StringTreeConcat(stc.children.map(simplifyAcc)) - case stc: StringTreeConst ⇒ stc + case str: StringTreeRepetition => StringTreeRepetition(simplifyAcc(str.child)) + case stc: StringTreeConcat => StringTreeConcat(stc.children.map(simplifyAcc)) + case stc: StringTreeConst => stc } } @@ -240,7 +240,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme if (repetitionElements.length <= 1) { // In case there is only one (new) repetition element, replace the children subtree.children.clear() - subtree.children.append(newChildren: _*) + subtree.children.appendAll(newChildren) subtree } else { val childrenOfReps = repetitionElements.map( @@ -263,13 +263,13 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme } subtree match { - case sto: StringTreeOr ⇒ processConcatOrOrCase(sto) - case stc: StringTreeConcat ⇒ processConcatOrOrCase(stc) - case StringTreeCond(cs) ⇒ + case sto: StringTreeOr => processConcatOrOrCase(sto) + case stc: StringTreeConcat => processConcatOrOrCase(stc) + case StringTreeCond(cs) => StringTreeCond(cs.map(groupRepetitionElementsAcc)) - case StringTreeRepetition(child, _, _) ⇒ + case StringTreeRepetition(child, _, _) => StringTreeRepetition(groupRepetitionElementsAcc(child)) - case stc: StringTreeConst ⇒ stc + case stc: StringTreeConst => stc } } 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 index a6c61a341e..bf6d266156 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala @@ -2,7 +2,6 @@ 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 @@ -13,7 +12,7 @@ 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 +import org.scalatestplus.junit.JUnitRunner /** * Computes the dominator tree of CFGs of a couple of methods and checks their sanity. @@ -31,7 +30,7 @@ class DominatorTreeTest extends AbstractCFGTest { private def getNextNonNullInstr(index: Int, code: Code): Int = { var foundIndex = index var found = false - for (i ← (index + 1).to(code.instructions.length)) { + for (i <- (index + 1).to(code.instructions.length)) { if (!found && code.instructions(i) != null) { foundIndex = i found = true @@ -56,7 +55,7 @@ class DominatorTreeTest extends AbstractCFGTest { printCFGOnFailure(m, code, cfg, Some(domTree)) { domTree.immediateDominators.zipWithIndex.foreach { - case (idom, index) ⇒ + case (idom, index) => if (index == 0) { idom should be(0) } else { @@ -76,12 +75,12 @@ class DominatorTreeTest extends AbstractCFGTest { printCFGOnFailure(m, code, cfg, Some(domTree)) { var index = 0 - code.foreachInstruction { next ⇒ + code.foreachInstruction { next => next match { - case _: IFNE | _: IF_ICMPNE ⇒ + case _: IFNE | _: IF_ICMPNE => val next = getNextNonNullInstr(index, code) domTree.immediateDominators(next) should be(index) - case _ ⇒ + case _ => } index += 1 } @@ -102,7 +101,7 @@ class DominatorTreeTest extends AbstractCFGTest { val loadOfReturn = loadOfReturnOption.get val indexOfLoadOfReturn = code.instructions.indexOf(loadOfReturn) val ifOfLoadOfReturn = code.instructions.reverse.zipWithIndex.find { - case (instr, i) ⇒ + case (instr, i) => i < indexOfLoadOfReturn && instr.isInstanceOf[IFEQ] } ifOfLoadOfReturn should not be ifOfLoadOfReturn.isEmpty 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 ac4f3d0984..8b410985a0 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,20 +1,19 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.br.string_definition -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 +import org.scalatest.funsuite.AnyFunSuite /** * Tests for [[StringConstancyLevel]] methods. * * @author Patrick Mell */ -@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class StringConstancyLevelTests extends FunSuite { +@org.junit.runner.RunWith(classOf[org.scalatestplus.junit.JUnitRunner]) +class StringConstancyLevelTests extends AnyFunSuite { test("tests that the more general string constancy level is computed correctly") { // Trivial cases 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 585697b1c6..4f7731473a 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 @@ -3,15 +3,14 @@ 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.value.ValueInformation 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.br.{DeclaredMethod, Method} +import org.opalj.tac.fpcf.properties.cg.Callees +import org.opalj.tac.fpcf.properties.cg.Callers import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter @@ -29,7 +28,7 @@ import org.opalj.tac.VirtualFunctionCall * @param fieldWriteThreshold See the documentation of * [[InterproceduralStringAnalysis#fieldWriteThreshold]]. */ -case class InterproceduralComputationState(entity: P, fieldWriteThreshold: Int = 100) { +case class InterproceduralComputationState(dm: DeclaredMethod, 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 7cb32bcc74..24cefdd022 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 @@ -3,7 +3,6 @@ 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.FinalEP import org.opalj.fpcf.FinalP @@ -16,17 +15,15 @@ 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.DeclaredMethodsKey -import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.{DeclaredMethodsKey, FieldAccessInformationKey, ProjectInformationKeys, SomeProject} import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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.tac.fpcf.properties.cg.Callers +import org.opalj.tac.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -51,6 +48,7 @@ import org.opalj.tac.ArrayLoad import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayLoadPreparer import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr +import org.opalj.tac.fpcf.analyses.cg.{RTATypeIterator, TypeIterator} /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -81,8 +79,10 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { - // TODO: Is it possible to make the following two parameters configurable from the outside? + //TODO: How do we pass the type iterator along? How do we decide which to use? + private val typeIterator: TypeIterator = new RTATypeIterator(project) + // 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 @@ -113,7 +113,7 @@ class InterproceduralStringAnalysis( state.entity, computeNewLowerBound(state), computeNewUpperBound(state), - state.dependees, + state.dependees.toSet, continuation(state) ) @@ -134,8 +134,8 @@ class InterproceduralStringAnalysis( ): StringConstancyProperty = StringConstancyProperty.lb def analyze(data: P): ProperPropertyComputationResult = { - val state = InterproceduralComputationState(data, fieldWriteThreshold) val dm = declaredMethods(data._2) + val state = InterproceduralComputationState(dm, data, fieldWriteThreshold) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { @@ -181,7 +181,7 @@ class InterproceduralStringAnalysis( if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, state + state.tac, ps, declaredMethods, fieldAccessInformation, state, typeIterator ) val interimState = state.copy() interimState.tac = state.tac @@ -190,7 +190,7 @@ class InterproceduralStringAnalysis( interimState.callers = state.callers interimState.params = state.params state.interimIHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, interimState + state.tac, ps, declaredMethods, fieldAccessInformation, interimState, typeIterator ) } @@ -259,7 +259,7 @@ class InterproceduralStringAnalysis( var sci = StringConstancyProperty.lb.stringConstancyInformation // 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) + val r = state.iHandler.processDefSite(defSites.head, state.params.toList.map(_.toList)) val sci = r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation return Result(state.entity, StringConstancyProperty(sci)) } @@ -270,13 +270,13 @@ class InterproceduralStringAnalysis( // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, stmts, uvar) if (dependentVars.nonEmpty) { - dependentVars.keys.foreach { nextVar ⇒ + dependentVars.keys.foreach { nextVar => val toAnalyze = (nextVar, state.entity._2) - dependentVars.foreach { case (k, v) ⇒ state.appendToVar2IndexMapping(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) - case _ ⇒ state.dependees = ep :: state.dependees + case FinalP(p) => return processFinalP(state, ep.e, p) + case _ => state.dependees = ep :: state.dependees } } } else { @@ -294,10 +294,10 @@ class InterproceduralStringAnalysis( // PathTransformer#pathToStringTree is involved in a mutual recursion) val isEmptyString = if (state.computedLeanPath.elements.length == 1) { state.computedLeanPath.elements.head match { - case FlatPathElement(i) ⇒ + case FlatPathElement(i) => state.fpe2sci.contains(i) && state.fpe2sci(i).length == 1 && state.fpe2sci(i).head == StringConstancyInformation.getNeutralElement - case _ ⇒ false + case _ => false } } else false @@ -333,32 +333,32 @@ class InterproceduralStringAnalysis( )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != eps.e) eps.pk match { - case TACAI.key ⇒ eps match { - case FinalP(tac: TACAI) ⇒ + 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) - case _ ⇒ + case _ => state.dependees = eps :: state.dependees getInterimResult(state) } - case Callees.key ⇒ eps match { - case FinalP(callees: Callees) ⇒ + case Callees.key => eps match { + case FinalP(callees: Callees) => state.callees = callees if (state.dependees.isEmpty) { determinePossibleStrings(state) } else { getInterimResult(state) } - case _ ⇒ + case _ => state.dependees = eps :: state.dependees getInterimResult(state) } - case Callers.key ⇒ eps match { - case FinalP(callers: Callers) ⇒ + case Callers.key => eps match { + case FinalP(callers: Callers) => state.callers = callers if (state.dependees.isEmpty) { registerParams(state) @@ -366,16 +366,16 @@ class InterproceduralStringAnalysis( } else { getInterimResult(state) } - case _ ⇒ + case _ => state.dependees = eps :: state.dependees getInterimResult(state) } - case StringConstancyProperty.key ⇒ + case StringConstancyProperty.key => eps match { - case FinalEP(entity, p: StringConstancyProperty) ⇒ + 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.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i => state.appendToInterimFpe2Sci(i, p.stringConstancyInformation) } // If necessary, update the parameter information with which the @@ -393,7 +393,7 @@ class InterproceduralStringAnalysis( _, p.stringConstancyInformation )) // Update the state - state.entity2Function(e).foreach { f ⇒ + 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 @@ -425,16 +425,16 @@ class InterproceduralStringAnalysis( } else { determinePossibleStrings(state) } - case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) ⇒ + case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) => state.dependees = eps :: state.dependees val uvar = eps.e.asInstanceOf[P]._1 - state.var2IndexMapping(uvar).foreach { i ⇒ + state.var2IndexMapping(uvar).foreach { i => state.appendToInterimFpe2Sci( i, ub.stringConstancyInformation, Some(uvar) ) } getInterimResult(state) - case _ ⇒ + case _ => state.dependees = eps :: state.dependees getInterimResult(state) } @@ -446,13 +446,13 @@ class InterproceduralStringAnalysis( state: InterproceduralComputationState, iHandler: InterproceduralInterpretationHandler ): Unit = path.elements.foreach { - case FlatPathElement(index) ⇒ + case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { iHandler.finalizeDefSite(index, state) } - case npe: NestedPathElement ⇒ + case npe: NestedPathElement => finalizePreparations(Path(npe.element.toList), state, iHandler) - case _ ⇒ + case _ => } /** @@ -511,35 +511,36 @@ class InterproceduralStringAnalysis( private def registerParams( state: InterproceduralComputationState ): Boolean = { - val callers = state.callers.callers(declaredMethods).toSeq + + val callers = state.callers.callers(state.dm)(typeIterator).iterator.toSeq if (callers.length > callersThreshold) { state.params.append( - state.entity._2.parameterTypes.map { - _: FieldType ⇒ StringConstancyInformation.lb - }.to[ListBuffer] + ListBuffer.from(state.entity._2.parameterTypes.map { + _: FieldType => StringConstancyInformation.lb + }) ) return false } 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 - 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(_, _, 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.zipWithIndex.foreach { - case (p, paramIndex) ⇒ + 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]) + state.params.append(ListBuffer.from(params.indices.map(_ => + StringConstancyInformation.getNeutralElement))) } // Recursively analyze supported types if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { @@ -547,9 +548,9 @@ class InterproceduralStringAnalysis( val eps = propertyStore(paramEntity, StringConstancyProperty.key) state.appendToVar2IndexMapping(paramEntity._1, paramIndex) eps match { - case FinalP(r) ⇒ + case FinalP(r) => state.params(methodIndex)(paramIndex) = r.stringConstancyInformation - case _ ⇒ + case _ => state.dependees = eps :: state.dependees hasIntermediateResult = true state.paramResultPositions(paramEntity) = (methodIndex, paramIndex) @@ -585,9 +586,9 @@ class InterproceduralStringAnalysis( var hasFinalResult = true p.elements.foreach { - case FlatPathElement(index) ⇒ + case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { - val eOptP = state.iHandler.processDefSite(index, state.params.toList) + val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq)) if (eOptP.isFinal) { val p = eOptP.asFinal.p.asInstanceOf[StringConstancyProperty] state.appendToFpe2Sci(index, p.stringConstancyInformation, reset = true) @@ -595,14 +596,14 @@ class InterproceduralStringAnalysis( hasFinalResult = false } } - case npe: NestedPathElement ⇒ + case npe: NestedPathElement => val subFinalResult = computeResultsForPath( Path(npe.element.toList), state ) if (hasFinalResult) { hasFinalResult = subFinalResult } - case _ ⇒ + case _ => } hasFinalResult @@ -642,7 +643,7 @@ class InterproceduralStringAnalysis( // 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 ⇒ + defSites.foreach { ds => children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) } Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) @@ -673,23 +674,23 @@ class InterproceduralStringAnalysis( private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { def hasExprParamUsage(expr: Expr[V]): Boolean = expr match { - case al: ArrayLoad[V] ⇒ + case al: ArrayLoad[V] => 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) - case be: BinaryExpr[V] ⇒ hasExprParamUsage(be.left) || hasExprParamUsage(be.right) - case _ ⇒ false + 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 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 + case NestedPathElement(subPath, _) => hasParamUsageAlongPath(Path(subPath.toList), stmts) + case _ => false } } @@ -707,17 +708,17 @@ class InterproceduralStringAnalysis( ): (ListBuffer[(V, Int)], Boolean) = { var encounteredTarget = false subpath match { - case fpe: FlatPathElement ⇒ + 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) ⇒ + case ExprStmt(_, outerExpr) => if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { val param = outerExpr.asVirtualFunctionCall.params.head.asVar - param.definedBy.filter(_ >= 0).foreach { ds ⇒ + param.definedBy.filter(_ >= 0).foreach { ds => val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append(( @@ -727,11 +728,11 @@ class InterproceduralStringAnalysis( } } } - case _ ⇒ + case _ => } (foundDependees, encounteredTarget) - case npe: NestedPathElement ⇒ - npe.element.foreach { nextSubpath ⇒ + case npe: NestedPathElement => + npe.element.foreach { nextSubpath => if (!encounteredTarget) { val (_, seen) = findDependeesAcc( nextSubpath, stmts, target, foundDependees, encounteredTarget @@ -740,7 +741,7 @@ class InterproceduralStringAnalysis( } } (foundDependees, encounteredTarget) - case _ ⇒ (foundDependees, encounteredTarget) + case _ => (foundDependees, encounteredTarget) } } @@ -761,13 +762,13 @@ class InterproceduralStringAnalysis( val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) var wasTargetSeen = false - path.elements.foreach { nextSubpath ⇒ + path.elements.foreach { nextSubpath => if (!wasTargetSeen) { val (currentDeps, encounteredTarget) = findDependeesAcc( nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false ) wasTargetSeen = encounteredTarget - currentDeps.foreach { nextPair ⇒ + currentDeps.foreach { nextPair => val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) @@ -791,7 +792,7 @@ object InterproceduralStringAnalysis { def registerParams(e: Entity, scis: ListBuffer[ListBuffer[StringConstancyInformation]]): Unit = { if (!paramInfos.contains(e)) { - paramInfos(e) = ListBuffer(scis: _*) + paramInfos(e) = scis } else { paramInfos(e).appendAll(scis) } @@ -854,7 +855,7 @@ object InterproceduralStringAnalysis { try { isSupportedType(v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava) } catch { - case _: Exception ⇒ false + case _: Exception => false } } @@ -877,9 +878,9 @@ object InterproceduralStringAnalysis { numberType: String ): StringConstancyInformation = { val possibleStrings = numberType match { - case "short" | "int" ⇒ StringConstancyInformation.IntValue - case "float" | "double" ⇒ StringConstancyInformation.FloatValue - case _ ⇒ StringConstancyInformation.UnknownWordSymbol + case "short" | "int" => StringConstancyInformation.IntValue + case "float" | "double" => StringConstancyInformation.FloatValue + case _ => StringConstancyInformation.UnknownWordSymbol } StringConstancyInformation(StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings) } @@ -929,4 +930,6 @@ object LazyInterproceduralStringAnalysis override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) -} + //TODO: Needs TAC key?? + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, FieldAccessInformationKey) +} \ No newline at end of file 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 e41ae3473a..4b60b503b5 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 @@ -3,7 +3,6 @@ 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 @@ -16,12 +15,12 @@ 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.analyses.{ProjectInformationKeys, SomeProject} import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.tac.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ExprStmt import org.opalj.tac.Stmt @@ -135,15 +134,15 @@ class IntraproceduralStringAnalysis( // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(leanPaths, stmts, uvar) if (dependentVars.nonEmpty) { - dependentVars.keys.foreach { nextVar ⇒ + dependentVars.keys.foreach { nextVar => val toAnalyze = (nextVar, data._2) val fpe2sci = mutable.Map[Int, StringConstancyInformation]() state = ComputationState(leanPaths, dependentVars, fpe2sci, tac) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalP(p) ⇒ + case FinalP(p) => return processFinalP(data, dependees.values.flatten, state, ep.e, p) - case _ ⇒ + case _ => if (!dependees.contains(data)) { dependees(data) = ListBuffer() } @@ -160,7 +159,7 @@ class IntraproceduralStringAnalysis( else { val interHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( - uvar.definedBy.toArray.sorted.map { ds ⇒ + uvar.definedBy.toArray.sorted.map { ds => val r = interHandler.processDefSite(ds).asFinal r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } @@ -172,7 +171,7 @@ class IntraproceduralStringAnalysis( data._1, StringConstancyProperty.ub, StringConstancyProperty.lb, - dependees.values.flatten, + dependees.values.flatten.toSet, continuation(data, dependees.values.flatten, state) ) } else { @@ -201,7 +200,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)) } + state.computedLeanPath, state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } ).reduce(true) Result(data, StringConstancyProperty(finalSci)) } else { @@ -209,7 +208,7 @@ class IntraproceduralStringAnalysis( data, StringConstancyProperty.ub, StringConstancyProperty.lb, - remDependees, + remDependees.toSet, continuation(data, remDependees, state) ) } @@ -229,11 +228,11 @@ class IntraproceduralStringAnalysis( 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 FinalP(p) => processFinalP(data, dependees, state, eps.e, p) + case InterimLUBP(lb, ub) => InterimResult( + data, lb, ub, dependees.toSet, continuation(data, dependees, state) ) - case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") + case _ => throw new IllegalStateException("Could not process the continuation successfully.") } /** @@ -250,17 +249,17 @@ class IntraproceduralStringAnalysis( ): (ListBuffer[(V, Int)], Boolean) = { var encounteredTarget = false subpath match { - case fpe: FlatPathElement ⇒ + 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) ⇒ + case ExprStmt(_, outerExpr) => if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { val param = outerExpr.asVirtualFunctionCall.params.head.asVar - param.definedBy.filter(_ >= 0).foreach { ds ⇒ + param.definedBy.filter(_ >= 0).foreach { ds => val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append(( @@ -270,11 +269,11 @@ class IntraproceduralStringAnalysis( } } } - case _ ⇒ + case _ => } (foundDependees, encounteredTarget) - case npe: NestedPathElement ⇒ - npe.element.foreach { nextSubpath ⇒ + case npe: NestedPathElement => + npe.element.foreach { nextSubpath => if (!encounteredTarget) { val (_, seen) = findDependeesAcc( nextSubpath, stmts, target, foundDependees, encounteredTarget @@ -283,7 +282,7 @@ class IntraproceduralStringAnalysis( } } (foundDependees, encounteredTarget) - case _ ⇒ (foundDependees, encounteredTarget) + case _ => (foundDependees, encounteredTarget) } } @@ -304,13 +303,13 @@ class IntraproceduralStringAnalysis( val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) var wasTargetSeen = false - path.elements.foreach { nextSubpath ⇒ + path.elements.foreach { nextSubpath => if (!wasTargetSeen) { val (currentDeps, encounteredTarget) = findDependeesAcc( nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false ) wasTargetSeen = encounteredTarget - currentDeps.foreach { nextPair ⇒ + currentDeps.foreach { nextPair => val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) @@ -366,4 +365,5 @@ object LazyIntraproceduralStringAnalysis override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + override def requiredProjectInformation: ProjectInformationKeys = Seq(EagerDetachedTACAIKey) } 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 be66ace930..8368815a99 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 @@ -3,7 +3,6 @@ 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.Property @@ -11,10 +10,9 @@ 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.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.{NoContext, StringConstancyProperty} +import org.opalj.tac.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -27,6 +25,7 @@ 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.cg.TypeIterator 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 @@ -86,13 +85,14 @@ abstract class AbstractStringInterpreter( */ protected def getMethodsForPC( implicit - pc: Int, ps: PropertyStore, callees: Callees, declaredMethods: DeclaredMethods + pc: Int, ps: PropertyStore, callees: Callees, typeIt: TypeIterator ): (List[Method], Boolean) = { var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() - callees.callees(pc).foreach { - case definedMethod: DefinedMethod ⇒ methods.append(definedMethod.definedMethod) - case _ ⇒ hasMethodWithUnknownBody = true + + callees.callees(NoContext, pc)(ps, typeIt).map(_.method).foreach { + case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) + case _ => hasMethodWithUnknownBody = true } (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) @@ -108,11 +108,11 @@ abstract class AbstractStringInterpreter( tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): List[Seq[Expr[V]]] = { val paramLists = ListBuffer[Seq[Expr[V]]]() - pcs.map(tac.pcToIndex).foreach { stmtIndex ⇒ + 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() + case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params + case Assignment(_, _, fc: FunctionCall[V]) => fc.params + case _ => Seq() } if (params.nonEmpty) { paramLists.append(params) @@ -139,12 +139,12 @@ abstract class AbstractStringInterpreter( funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, entity2function: mutable.Map[P, ListBuffer[FunctionCall[V]]] - ): NonFinalFunctionArgs = params.zipWithIndex.map { - case (nextParamList, outerIndex) ⇒ - nextParamList.zipWithIndex.map { - case (nextParam, middleIndex) ⇒ - nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { - case (ds, innerIndex) ⇒ + ): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { + case (nextParamList, outerIndex) => + ListBuffer.from(nextParamList.zipWithIndex.map { + case (nextParam, middleIndex) => + ListBuffer.from(nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { + case (ds, innerIndex) => val ep = iHandler.processDefSite(ds) if (ep.isRefinable) { if (!functionArgsPos.contains(funCall)) { @@ -158,9 +158,9 @@ abstract class AbstractStringInterpreter( entity2function(e).append(funCall) } ep - }.to[ListBuffer] - }.to[ListBuffer] - }.to[ListBuffer] + }) + }) + }) /** * This function checks whether the interpretation of parameters, as, e.g., produced by @@ -180,15 +180,15 @@ abstract class AbstractStringInterpreter( */ protected def convertEvaluatedParameters( evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, Property]]]] - ): ListBuffer[ListBuffer[StringConstancyInformation]] = evaluatedParameters.map { paramList ⇒ - paramList.map { param ⇒ + ): ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer.from(evaluatedParameters.map { paramList => + ListBuffer.from(paramList.map { param => StringConstancyInformation.reduceMultiple( param.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].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 bad135fd9b..23b5b9064c 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 @@ -90,11 +90,11 @@ object InterpretationHandler { */ def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { - case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ + case VirtualFunctionCall(_, clazz, _, name, _, _, _) => val className = clazz.mostPreciseObjectType.fqn (className == "java/lang/StringBuilder" || className == "java/lang/StringBuffer") && name == "toString" - case _ ⇒ false + case _ => false } /** @@ -136,11 +136,11 @@ object InterpretationHandler { */ def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { expr match { - case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ + case VirtualFunctionCall(_, clazz, _, name, _, _, _) => val className = clazz.toJavaClass.getName (className == "java.lang.StringBuilder" || className == "java.lang.StringBuffer") && name == "append" - case _ ⇒ false + case _ => false } } @@ -156,16 +156,16 @@ object InterpretationHandler { } val defSites = ListBuffer[Int]() - val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.filter(_ >= 0).toArray: _*) + val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.filter(_ >= 0).toList: _*) val seenElements: mutable.Map[Int, Unit] = mutable.Map() while (stack.nonEmpty) { val next = stack.pop() stmts(next) match { - case a: Assignment[V] ⇒ + case a: Assignment[V] => a.expr match { - case _: New ⇒ + case _: New => defSites.append(next) - case vfc: VirtualFunctionCall[V] ⇒ + case vfc: VirtualFunctionCall[V] => 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 @@ -174,13 +174,13 @@ object InterpretationHandler { } else { defSites.append(next) } - case _: GetField[V] ⇒ + case _: GetField[V] => defSites.append(next) - case _ ⇒ // E.g., NullExpr + case _ => // E.g., NullExpr } - case _ ⇒ + case _ => } - seenElements(next) = Unit + seenElements(next) = () } defSites.sorted.toList @@ -196,11 +196,11 @@ object InterpretationHandler { */ def findDefSiteOfInit(duvar: V, stmts: Array[Stmt[V]]): List[Int] = { val defSites = ListBuffer[Int]() - duvar.definedBy.foreach { ds ⇒ + duvar.definedBy.foreach { ds => defSites.appendAll(stmts(ds).asAssignment.expr match { - case vfc: VirtualFunctionCall[V] ⇒ findDefSiteOfInitAcc(vfc, stmts) + case vfc: VirtualFunctionCall[V] => findDefSiteOfInitAcc(vfc, stmts) // The following case is, e.g., for {NonVirtual, Static}FunctionCalls - case _ ⇒ List(ds) + case _ => List(ds) }) } // If no init sites could be determined, use the definition sites of the UVar @@ -222,26 +222,26 @@ object InterpretationHandler { val news = ListBuffer[New]() // HINT: It might be that the search has to be extended to further cases - duvar.definedBy.filter(_ >= 0).foreach { ds ⇒ + duvar.definedBy.filter(_ >= 0).foreach { ds => stmts(ds) match { // E.g., a call to `toString` or `append` - case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ - vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs ⇒ + case Assignment(_, _, vfc: VirtualFunctionCall[V]) => + vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs => stmts(innerDs) match { - case Assignment(_, _, expr: New) ⇒ + case Assignment(_, _, expr: New) => news.append(expr) - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + case Assignment(_, _, expr: VirtualFunctionCall[V]) => val exprReceiverVar = expr.receiver.asVar // The "if" is to avoid endless recursion if (duvar.definedBy != exprReceiverVar.definedBy) { news.appendAll(findNewOfVar(exprReceiverVar, stmts)) } - case _ ⇒ + case _ => } } - case Assignment(_, _, newExpr: New) ⇒ + case Assignment(_, _, newExpr: New) => news.append(newExpr) - case _ ⇒ + case _ => } } 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 f6730147b1..62415cfdca 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 @@ -49,9 +49,9 @@ class BinaryExprInterpreter( */ 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 + case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt + case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat + case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala index 2db756ba09..7e85200237 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala @@ -58,8 +58,8 @@ class ArrayLoadPreparer( instr, state.tac.stmts ) - allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { - case (ds, ep) ⇒ + allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { + case (ds, ep) => if (ep.isFinal) { val p = ep.asFinal.p.asInstanceOf[StringConstancyProperty] state.appendToFpe2Sci(ds, p.stringConstancyInformation) @@ -68,7 +68,7 @@ class ArrayLoadPreparer( } // Add information of parameters - defSites.filter(_ < 0).foreach { ds ⇒ + defSites.filter(_ < 0).foreach { ds => val paramPos = Math.abs(ds + 2) // lb is the fallback value val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) @@ -120,22 +120,22 @@ object ArrayLoadPreparer { val allDefSites = ListBuffer[Int]() val defSites = instr.arrayRef.asVar.definedBy.toArray - defSites.filter(_ >= 0).sorted.foreach { next ⇒ + 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 ⇒ + } 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 + case Assignment(_, _, _: ArrayLoad[V]) => true + case _ => false } - } foreach { f: Int ⇒ + } foreach { f: Int => val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy allDefSites.appendAll(defs.toArray) } 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 17c4b6a9f5..fc4a44c1a8 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 @@ -69,7 +69,7 @@ class InterproceduralFieldInterpreter( var hasInit = false val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() writeAccesses.foreach { - case (m, pcs) ⇒ pcs.foreach { pc ⇒ + case (m, pcs) => pcs.foreach { pc => if (m.name == "" || m.name == "") { hasInit = true } @@ -78,7 +78,7 @@ class InterproceduralFieldInterpreter( EPK(state.entity, StringConstancyProperty.key) } else { tac match { - case Some(methodTac) ⇒ + case Some(methodTac) => val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) val entity = (extractUVarFromPut(stmt), m) val eps = ps(entity, StringConstancyProperty.key) @@ -92,7 +92,7 @@ class InterproceduralFieldInterpreter( state.appendToVar2IndexMapping(entity._1, -1) } eps - case _ ⇒ + case _ => // No TAC available FinalEP(defSitEntity, StringConstancyProperty.lb) } @@ -140,9 +140,9 @@ class InterproceduralFieldInterpreter( * [[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!") + 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/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 76814a6b66..d38e5d30c3 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 @@ -11,7 +11,7 @@ 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.cg.Callees +import org.opalj.tac.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 @@ -50,6 +50,7 @@ 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.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NewArrayFinalizer /** @@ -68,7 +69,8 @@ class InterproceduralInterpretationHandler( ps: PropertyStore, declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState + state: InterproceduralComputationState, + typeIterator: TypeIterator ) extends InterpretationHandler(tac) { /** @@ -98,35 +100,35 @@ class InterproceduralInterpretationHandler( return FinalEP(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down - processedDefSites(defSite) = Unit + processedDefSites(defSite) = () val callees = state.callees stmts(defSite) match { - 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]) ⇒ + 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]) => processArrayLoad(expr, defSite, params) - case Assignment(_, _, expr: NewArray[V]) ⇒ + 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) - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) - case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) - case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ + case Assignment(_, _, expr: New) => processNew(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]) => processStaticFunctionCall(expr, defSite, params) - case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite, params) - case Assignment(_, _, expr: BinaryExpr[V]) ⇒ processBinaryExpr(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ + case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) - case Assignment(_, _, expr: GetField[V]) ⇒ processGetField(expr, defSite) - case vmc: VirtualMethodCall[V] ⇒ + case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) + case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) - case nvmc: NonVirtualMethodCall[V] ⇒ processNonVirtualMethodCall(nvmc, defSite) - case _ ⇒ + case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) + case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) } @@ -140,10 +142,10 @@ class InterproceduralInterpretationHandler( constExpr: SimpleValueConst, defSite: Int ): 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) - case sc ⇒ new StringConstInterpreter(cfg, this).interpret( + 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 ) } @@ -214,7 +216,7 @@ class InterproceduralInterpretationHandler( params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( - cfg, this, ps, state, declaredMethods, params + cfg, this, ps, state, declaredMethods, params, typeIterator ).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 @@ -253,7 +255,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 + cfg, this, ps, state, params, declaredMethods, typeIterator ).interpret(expr, defSite) if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -301,7 +303,7 @@ class InterproceduralInterpretationHandler( expr: NonVirtualFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods + cfg, this, ps, state, declaredMethods, typeIterator ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -333,10 +335,10 @@ class InterproceduralInterpretationHandler( cfg, this, ps, state, declaredMethods ).interpret(nvmc, defSite) r match { - case FinalEP(_, p: StringConstancyProperty) ⇒ + case FinalEP(_, p: StringConstancyProperty) => state.appendToInterimFpe2Sci(defSite, p.stringConstancyInformation) state.appendToFpe2Sci(defSite, p.stringConstancyInformation) - case _ ⇒ + case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) processedDefSites.remove(defSite) } @@ -382,28 +384,28 @@ class InterproceduralInterpretationHandler( defSite: Int, state: InterproceduralComputationState ): Unit = { if (defSite < 0) { - state.appendToFpe2Sci(defSite, getParam(state.params, defSite), reset = true) + state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) } else { stmts(defSite) match { - case nvmc: NonVirtualMethodCall[V] ⇒ + case nvmc: NonVirtualMethodCall[V] => NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) - case Assignment(_, _, al: ArrayLoad[V]) ⇒ + case Assignment(_, _, al: ArrayLoad[V]) => ArrayLoadFinalizer(state, cfg).finalizeInterpretation(al, defSite) - case Assignment(_, _, na: NewArray[V]) ⇒ + case Assignment(_, _, na: NewArray[V]) => NewArrayFinalizer(state, cfg).finalizeInterpretation(na, defSite) - case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ + case Assignment(_, _, vfc: VirtualFunctionCall[V]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ + case ExprStmt(_, vfc: VirtualFunctionCall[V]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case Assignment(_, _, fr: FieldRead[V]) ⇒ + case Assignment(_, _, fr: FieldRead[V]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case ExprStmt(_, fr: FieldRead[V]) ⇒ + case ExprStmt(_, fr: FieldRead[V]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case Assignment(_, _, sfc: StaticFunctionCall[V]) ⇒ + case Assignment(_, _, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) - case ExprStmt(_, sfc: StaticFunctionCall[V]) ⇒ + case ExprStmt(_, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) - case _ ⇒ state.appendToFpe2Sci( + case _ => state.appendToFpe2Sci( defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true ) } @@ -422,9 +424,10 @@ object InterproceduralInterpretationHandler { ps: PropertyStore, declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState + state: InterproceduralComputationState, + typeIterator: TypeIterator ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - tac, ps, declaredMethods, fieldAccessInformation, state + tac, ps, declaredMethods, fieldAccessInformation, state, typeIterator ) } \ 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 7ed25ed155..65c67a78ff 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 @@ -15,6 +15,7 @@ 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.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState @@ -31,7 +32,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, - declaredMethods: DeclaredMethods + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] @@ -48,7 +50,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val methods = getMethodsForPC(instr.pc, ps, state.callees, declaredMethods) + val methods = getMethodsForPC(instr.pc, ps, state.callees, typeIterator) if (methods._1.isEmpty) { // No methods available => Return lower bound return FinalEP(instr, StringConstancyProperty.lb) @@ -65,7 +67,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( // is approximated with the lower bound FinalEP(instr, StringConstancyProperty.lb) } else { - val results = returns.map { ret ⇒ + val results = returns.map { ret => val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) 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 5746452b1e..692cd9c539 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 @@ -55,8 +55,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( ): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { - case "" ⇒ interpretInit(instr, e) - case _ ⇒ FinalEP(e, StringConstancyProperty.getNeutralElement) + case "" => interpretInit(instr, e) + case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -71,14 +71,14 @@ class InterproceduralNonVirtualMethodCallInterpreter( init: NonVirtualMethodCall[V], defSite: Integer ): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { - case 0 ⇒ FinalEP(defSite, StringConstancyProperty.getNeutralElement) - case _ ⇒ - val results = init.params.head.asVar.definedBy.map { ds: Int ⇒ + 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.isFinal)) { // Final result is available - val reduced = StringConstancyInformation.reduceMultiple(results.map { r ⇒ + val reduced = StringConstancyInformation.reduceMultiple(results.map { r => val prop = r._2.asFinal.p.asInstanceOf[StringConstancyProperty] prop.stringConstancyInformation }) @@ -86,16 +86,16 @@ class InterproceduralNonVirtualMethodCallInterpreter( } else { // Some intermediate results => register necessary information from final // results and return an intermediate result - val returnIR = results.find(r ⇒ !r._2.isFinal).get._2 + val returnIR = results.find(r => !r._2.isFinal).get._2 results.foreach { - case (ds, r) ⇒ + case (ds, r) => if (r.isFinal) { val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] state.appendToFpe2Sci( ds, p.stringConstancyInformation, reset = true ) } - case _ ⇒ + 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 56789f61e6..3ec874cbcd 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 @@ -2,7 +2,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import scala.util.Try - import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK @@ -10,7 +9,7 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore 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.{NoContext, StringConstancyProperty} import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt @@ -20,6 +19,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractString import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -37,7 +37,8 @@ class InterproceduralStaticFunctionCallInterpreter( ps: PropertyStore, state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] @@ -71,7 +72,7 @@ class InterproceduralStaticFunctionCallInterpreter( private def processStringValueOf( call: StaticFunctionCall[V] ): EOptionP[Entity, StringConstancyProperty] = { - val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds ⇒ + val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds => exprHandler.processDefSite(ds, params) } val interim = results.find(_.isRefinable) @@ -79,11 +80,11 @@ class InterproceduralStaticFunctionCallInterpreter( interim.get } else { // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r ⇒ + val scis = results.map { r => r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { - scis.map { sci ⇒ + scis.map { sci => if (Try(sci.possibleStrings.toInt).isSuccess) { sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) } else { @@ -105,7 +106,7 @@ class InterproceduralStaticFunctionCallInterpreter( instr: StaticFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC( - instr.pc, ps, state.callees, declaredMethods + instr.pc, ps, state.callees, typeIterator ) // Static methods cannot be overwritten, thus 1) we do not need the second return value of @@ -118,11 +119,11 @@ class InterproceduralStaticFunctionCallInterpreter( val m = methods._1.head val (_, tac) = getTACAI(ps, m, state) - val directCallSites = state.callees.directCallSites()(ps, declaredMethods) + val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) val relevantPCs = directCallSites.filter { - case (_, calledMethods) ⇒ - calledMethods.exists(m ⇒ - m.name == instr.name && m.declaringClassType == instr.declaringClass) + case (_, calledMethods) => + calledMethods.exists(m => + m.method.name == instr.name && m.method.declaringClassType == instr.declaringClass) }.keys // Collect all parameters; either from the state if the interpretation of instr was started @@ -140,11 +141,11 @@ class InterproceduralStaticFunctionCallInterpreter( ) } // Continue only when all parameter information are available - val nonFinalResults = getNonFinalParameters(params) + val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (nonFinalResults.nonEmpty) { if (tac.isDefined) { val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) - returns.foreach { ret ⇒ + returns.foreach { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) val eps = ps(entity, StringConstancyProperty.key) state.dependees = eps :: state.dependees @@ -158,7 +159,7 @@ class InterproceduralStaticFunctionCallInterpreter( state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params) + val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis @@ -168,7 +169,7 @@ class InterproceduralStaticFunctionCallInterpreter( // is approximated with the lower bound FinalEP(instr, StringConstancyProperty.lb) } else { - val results = returns.map { ret ⇒ + val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) 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 29808af2c8..3c17e653f3 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 @@ -9,7 +9,7 @@ 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.fpcf.properties.cg.Callees import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall @@ -51,10 +51,10 @@ class InterproceduralVirtualMethodCallInterpreter( */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { - case "setLength" ⇒ StringConstancyInformation( + case "setLength" => StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET ) - case _ ⇒ StringConstancyInformation.getNeutralElement + case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } 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 bfcc1a238a..eb232514e4 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 @@ -56,10 +56,10 @@ class NewArrayPreparer( 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 => 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 ⇒ + 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) @@ -69,7 +69,7 @@ class NewArrayPreparer( } // Add information of parameters - arrValuesDefSites.filter(_ < 0).foreach { ds ⇒ + arrValuesDefSites.filter(_ < 0).foreach { ds => val paramPos = Math.abs(ds + 2) // lb is the fallback value val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) 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 75f6778094..841b18e103 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 @@ -12,7 +12,7 @@ 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.{NoContext, 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 @@ -23,6 +23,7 @@ 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.ReturnValue +import org.opalj.tac.fpcf.analyses.cg.TypeIterator 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 @@ -42,7 +43,8 @@ class VirtualFunctionCallPreparationInterpreter( ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]] + params: List[Seq[StringConstancyInformation]], + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -78,14 +80,14 @@ class VirtualFunctionCallPreparationInterpreter( */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val result = instr.name match { - case "append" ⇒ interpretAppendCall(instr, defSite) - case "toString" ⇒ interpretToStringCall(instr) - case "replace" ⇒ interpretReplaceCall(instr) - case _ ⇒ + case "append" => interpretAppendCall(instr, defSite) + case "toString" => interpretToStringCall(instr) + case "replace" => interpretReplaceCall(instr) + case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ + case obj: ObjectType if obj.fqn == "java/lang/String" => interpretArbitraryCall(instr, defSite) - case _ ⇒ + case _ => val e: Integer = defSite FinalEP(e, StringConstancyProperty.lb) } @@ -109,20 +111,21 @@ class VirtualFunctionCallPreparationInterpreter( instr: T, defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val (methods, _) = getMethodsForPC( - instr.pc, ps, state.callees, declaredMethods + instr.pc, ps, state.callees, typeIterator ) if (methods.isEmpty) { return FinalEP(instr, StringConstancyProperty.lb) } - - val directCallSites = state.callees.directCallSites()(ps, declaredMethods) + //TODO: Type Iterator! + val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) 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 + case (_, calledMethods) => calledMethods.exists { m => + val mClassName = m.method.declaringClassType.toJava + m.method.name == instr.name && mClassName == instrClassName } }.keys @@ -141,7 +144,7 @@ class VirtualFunctionCallPreparationInterpreter( ) } // Continue only when all parameter information are available - val nonFinalResults = getNonFinalParameters(params) + val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (nonFinalResults.nonEmpty) { state.nonFinalFunctionArgs(instr) = params return nonFinalResults.head @@ -149,8 +152,8 @@ class VirtualFunctionCallPreparationInterpreter( state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params) - val results = methods.map { nextMethod ⇒ + val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + val results = methods.map { nextMethod => val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { state.methodPrep2defSite.remove(nextMethod) @@ -160,15 +163,15 @@ class VirtualFunctionCallPreparationInterpreter( // guaranteed to throw an exception FinalEP(instr, StringConstancyProperty.lb) } else { - val results = returns.map { ret ⇒ + 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: FinalEP[P, StringConstancyProperty] ⇒ + case r: FinalEP[P, StringConstancyProperty] => state.appendToFpe2Sci(defSite, r.p.stringConstancyInformation) r - case _ ⇒ + case _ => state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) eps @@ -210,7 +213,7 @@ class VirtualFunctionCallPreparationInterpreter( return appendResult } - val receiverScis = receiverResults.map { r ⇒ + val receiverScis = receiverResults.map { r => val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] p.stringConstancyInformation } @@ -262,18 +265,18 @@ class VirtualFunctionCallPreparationInterpreter( ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted - val allResults = defSites.map(ds ⇒ (ds, exprHandler.processDefSite(ds, params))) + val allResults = defSites.map(ds => (ds, exprHandler.processDefSite(ds, params))) val finalResults = allResults.filter(_._2.isFinal) val finalResultsWithoutNeutralElements = finalResults.filter { - case (_, FinalEP(_, p: StringConstancyProperty)) ⇒ + case (_, FinalEP(_, p: StringConstancyProperty)) => !p.stringConstancyInformation.isTheNeutralElement - case _ ⇒ false + case _ => false } 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 ⇒ + finalResultsWithoutNeutralElements.foreach { next => val p = next._2.asFinal.p.asInstanceOf[StringConstancyProperty] val sci = p.stringConstancyInformation state.appendToFpe2Sci(next._1, sci) @@ -331,7 +334,7 @@ class VirtualFunctionCallPreparationInterpreter( val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values - case ComputationalTypeInt ⇒ + 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 && @@ -339,7 +342,7 @@ class VirtualFunctionCallPreparationInterpreter( if (defSitesValueSci.isTheNeutralElement) { StringConstancyProperty.lb.stringConstancyInformation } else { - val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci ⇒ + val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => if (isIntegerValue(sci.possibleStrings)) { sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) } else { @@ -351,10 +354,10 @@ class VirtualFunctionCallPreparationInterpreter( } else { newValueSci } - case ComputationalTypeFloat ⇒ + case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat // Otherwise, try to compute - case _ ⇒ + case _ => newValueSci } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 71b3ae04f9..8c2b055144 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -31,14 +31,14 @@ class ArrayLoadFinalizer( instr, state.tac.stmts ) - allDefSites.foreach { ds ⇒ + allDefSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { state.iHandler.finalizeDefSite(ds, state) } } state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( - allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds ⇒ + allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds => state.fpe2sci(ds) } )) 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 d30b6e53c2..de96dffe41 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 @@ -23,7 +23,7 @@ class NonVirtualMethodCallFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val toAppend = if (instr.params.nonEmpty) { - instr.params.head.asVar.definedBy.toArray.foreach { ds ⇒ + instr.params.head.asVar.definedBy.toArray.foreach { ds => if (!state.fpe2sci.contains(ds)) { state.iHandler.finalizeDefSite(ds, state) } 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 index 8afee79f3a..e4cbecbe81 100644 --- 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 @@ -29,7 +29,7 @@ class StaticFunctionCallFinalizer( // 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 ⇒ + defSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { state.iHandler.finalizeDefSite(ds, state) } 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 c41153dd3a..36360ad6d9 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 @@ -29,9 +29,9 @@ class VirtualFunctionCallFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { instr.name match { - case "append" ⇒ finalizeAppend(instr, defSite) - case "toString" ⇒ finalizeToString(instr, defSite) - case _ ⇒ state.appendToFpe2Sci( + case "append" => finalizeAppend(instr, defSite) + case "toString" => finalizeToString(instr, defSite) + case _ => state.appendToFpe2Sci( defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true ) } @@ -44,13 +44,13 @@ class VirtualFunctionCallFinalizer( */ private def finalizeAppend(instr: T, defSite: Int): Unit = { val receiverDefSites = instr.receiver.asVar.definedBy.toArray.sorted - receiverDefSites.foreach { ds ⇒ + receiverDefSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { state.iHandler.finalizeDefSite(ds, state) } } val receiverSci = StringConstancyInformation.reduceMultiple( - receiverDefSites.flatMap { s ⇒ + 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 @@ -61,7 +61,7 @@ class VirtualFunctionCallFinalizer( ) val paramDefSites = instr.params.head.asVar.definedBy.toArray.sorted - paramDefSites.foreach { ds ⇒ + paramDefSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { state.iHandler.finalizeDefSite(ds, state) } @@ -93,13 +93,13 @@ class VirtualFunctionCallFinalizer( private def finalizeToString(instr: T, defSite: Int): Unit = { val dependeeSites = instr.receiver.asVar.definedBy - dependeeSites.foreach { nextDependeeSite ⇒ + dependeeSites.foreach { nextDependeeSite => if (!state.fpe2sci.contains(nextDependeeSite)) { state.iHandler.finalizeDefSite(nextDependeeSite, state) } } val finalSci = StringConstancyInformation.reduceMultiple( - dependeeSites.toArray.flatMap { ds ⇒ state.fpe2sci(ds) } + 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) 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 0724fa5910..4ebd6a556c 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 @@ -42,27 +42,27 @@ class IntraproceduralArrayInterpreter( val children = ListBuffer[StringConstancyInformation]() // Loop over all possible array values val defSites = instr.arrayRef.asVar.definedBy.toArray - defSites.filter(_ >= 0).sorted.foreach { next ⇒ + 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 ⇒ + } 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 => n.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } // Process ArrayLoads sortedArrDeclUses.filter { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) ⇒ true - case _ ⇒ false + case Assignment(_, _, _: ArrayLoad[V]) => true + case _ => false } - } foreach { f: Int ⇒ + } 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 => n.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } 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 d4b87324cb..ae19617d1d 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 @@ -67,48 +67,48 @@ class IntraproceduralInterpretationHandler( } else if (processedDefSites.contains(defSite)) { return FinalEP(e, StringConstancyProperty.getNeutralElement) } - processedDefSites(defSite) = Unit + processedDefSites(defSite) = () val result: EOptionP[Entity, Property] = stmts(defSite) match { - case Assignment(_, _, expr: StringConst) ⇒ + case Assignment(_, _, expr: StringConst) => new StringConstInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: IntConst) ⇒ + case Assignment(_, _, expr: IntConst) => new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: FloatConst) ⇒ + case Assignment(_, _, expr: FloatConst) => new FloatValueInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: DoubleConst) ⇒ + case Assignment(_, _, expr: DoubleConst) => new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: ArrayLoad[V]) ⇒ + case Assignment(_, _, expr: ArrayLoad[V]) => new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: New) ⇒ + case Assignment(_, _, expr: New) => new NewInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + case Assignment(_, _, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ + case Assignment(_, _, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) ⇒ + case Assignment(_, _, expr: BinaryExpr[V]) => new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => new IntraproceduralNonVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: GetField[V]) ⇒ + case Assignment(_, _, expr: GetField[V]) => new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) - case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + case ExprStmt(_, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + case ExprStmt(_, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case vmc: VirtualMethodCall[V] ⇒ + case vmc: VirtualMethodCall[V] => new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) - case nvmc: NonVirtualMethodCall[V] ⇒ + case nvmc: NonVirtualMethodCall[V] => new IntraproceduralNonVirtualMethodCallInterpreter( cfg, this ).interpret(nvmc, defSite) - case _ ⇒ FinalEP(e, StringConstancyProperty.getNeutralElement) + case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } result } 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 3868f54cf3..a09c07333c 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 @@ -52,8 +52,8 @@ class IntraproceduralNonVirtualMethodCallInterpreter( instr: NonVirtualMethodCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { - case "" ⇒ interpretInit(instr) - case _ ⇒ StringConstancyProperty.getNeutralElement + case "" => interpretInit(instr) + case _ => StringConstancyProperty.getNeutralElement } FinalEP(instr, prop) } @@ -67,10 +67,10 @@ class IntraproceduralNonVirtualMethodCallInterpreter( */ private def interpretInit(init: NonVirtualMethodCall[V]): StringConstancyProperty = { init.params.size match { - case 0 ⇒ StringConstancyProperty.getNeutralElement - case _ ⇒ + case 0 => StringConstancyProperty.getNeutralElement + case _ => val scis = ListBuffer[StringConstancyInformation]() - init.params.head.asVar.definedBy.foreach { ds ⇒ + init.params.head.asVar.definedBy.foreach { ds => val r = exprHandler.processDefSite(ds).asFinal scis.append( r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation 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 0cc3b663aa..d25a6c0d22 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 @@ -67,20 +67,20 @@ class IntraproceduralVirtualFunctionCallInterpreter( */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val property = instr.name match { - case "append" ⇒ interpretAppendCall(instr) - case "toString" ⇒ interpretToStringCall(instr) - case "replace" ⇒ interpretReplaceCall(instr) - case _ ⇒ + 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" ⇒ + case obj: ObjectType if obj.fqn == "java/lang/String" => StringConstancyProperty.lb - case FloatType | DoubleType ⇒ + case FloatType | DoubleType => StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.FloatValue )) - case _ ⇒ StringConstancyProperty.getNeutralElement + case _ => StringConstancyProperty.getNeutralElement } } @@ -133,10 +133,10 @@ class IntraproceduralVirtualFunctionCallInterpreter( ): 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 { ds ⇒ + val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => val r = exprHandler.processDefSite(ds) r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }.filter { sci ⇒ !sci.isTheNeutralElement } + }.filter { sci => !sci.isTheNeutralElement } val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement else scis.head StringConstancyProperty(sci) @@ -166,7 +166,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( val sci = value.stringConstancyInformation val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values - case ComputationalTypeInt ⇒ + 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 && @@ -177,14 +177,14 @@ class IntraproceduralVirtualFunctionCallInterpreter( } else { sci } - case ComputationalTypeFloat | ComputationalTypeDouble ⇒ + case ComputationalTypeFloat | ComputationalTypeDouble => if (sci.constancyLevel == StringConstancyLevel.CONSTANT) { sci } else { InterpretationHandler.getConstancyInfoForDynamicFloat } // Otherwise, try to compute - case _ ⇒ + case _ => sci } 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 f35fbb332f..2862daff13 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 @@ -51,10 +51,10 @@ class IntraproceduralVirtualMethodCallInterpreter( */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { - case "setLength" ⇒ StringConstancyInformation( + case "setLength" => StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET ) - case _ ⇒ StringConstancyInformation.getNeutralElement + case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } 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 a8d094f7ce..583caaef94 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 @@ -3,7 +3,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG @@ -65,23 +64,23 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * whole conditional as described above. */ private def getStartAndEndIndexOfCondWithAlternative( - branchingSite: Int, processedIfs: mutable.Map[Int, Unit.type] + branchingSite: Int, processedIfs: mutable.Map[Int, Unit] ): (Int, Int) = { - processedIfs(branchingSite) = Unit + processedIfs(branchingSite) = () 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 + case bb: BasicBlock => bb.startPC // Handle Catch Nodes? - case _ ⇒ -1 + case _ => -1 }.max var containsIf = false - for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { if (cfg.code.instructions(i).isInstanceOf[If[V]]) { - processedIfs(i) = Unit + processedIfs(i) = () containsIf = true } } @@ -94,19 +93,19 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // surrounding loop var isRelevantGoto = false val relevantGoTo: Option[Goto] = cfg.code.instructions(nextBlock - 1) match { - case goto: Goto ⇒ + case goto: Goto => // 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 ⇒ + isRelevantGoto = !cfg.findNaturalLoops().exists { l => l.head == gotoSite } Some(goto) - case _ ⇒ None + case _ => None } relevantGoTo match { - case Some(goto) ⇒ + 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 @@ -122,28 +121,28 @@ 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) ⇒ + case Some(cs: CatchNode) => endSite = cs.endPC if (endSite == -1) { endSite = nextBlock } - case _ ⇒ + case _ => endSite = if (nextBlock > branchingSite) nextBlock - 1 else cfg.findNaturalLoops().find { _.head == goto.targetStmt }.get.last } } - case _ ⇒ + case _ => // No goto available => Jump after next block var nextIf: Option[If[V]] = None var i = nextBlock while (i < cfg.code.instructions.length && nextIf.isEmpty) { cfg.code.instructions(i) match { - case iff: If[V] ⇒ + case iff: If[V] => nextIf = Some(iff) - processedIfs(i) = Unit - case _ ⇒ + processedIfs(i) = () + case _ => } i += 1 } @@ -178,19 +177,19 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * whole conditional as described above. */ private def getStartAndEndIndexOfCondWithoutAlternative( - branchingSite: Int, processedIfs: mutable.Map[Int, Unit.type] + branchingSite: Int, processedIfs: mutable.Map[Int, Unit] ): (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 + case bb: BasicBlock => bb.startPC // Handle Catch Nodes? - case _ ⇒ -1 + case _ => -1 }.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)) { + for (i <- cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { // 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) { @@ -200,7 +199,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var endIndex = nextPossibleIfBlock - 1 if (nextIfIndex > -1 && !isHeadOfLoop(nextIfIndex, cfg.findNaturalLoops(), cfg)) { - processedIfs(nextIfIndex) = Unit + processedIfs(nextIfIndex) = () val (_, newEndIndex) = getStartAndEndIndexOfCondWithoutAlternative( nextIfIndex, processedIfs ) @@ -215,7 +214,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val toVisit = mutable.Stack[Int](branchingSite) while (toVisit.nonEmpty) { val popped = toVisit.pop() - seenElements(popped) = Unit + seenElements(popped) = () val relevantSuccessors = cfg.bb(popped).successors.filter { _.isInstanceOf[BasicBlock] }.map(_.asBasicBlock) @@ -223,7 +222,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { endIndex = cfg.bb(popped).endPC toVisit.clear() } else { - toVisit.pushAll(relevantSuccessors.filter { s ⇒ + toVisit.pushAll(relevantSuccessors.filter { s => s.nodeId != ifTarget && !seenElements.contains(s.nodeId) }.map(_.startPC)) } @@ -232,7 +231,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 ⇒ + val inTryBlocks = cfg.catchNodes.filter { cn => branchingSite >= cn.startPC && branchingSite <= cn.endPC } if (inTryBlocks.nonEmpty) { @@ -245,14 +244,14 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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)) { + case pred: BasicBlock => + for (i <- pred.startPC.to(pred.endPC)) { if (cfg.code.instructions(i).isInstanceOf[If[V]]) { - processedIfs(i) = Unit + processedIfs(i) = () } } // How about CatchNodes? - case _ ⇒ + case _ => } (branchingSite, endIndex) @@ -297,7 +296,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // present); a map is used for faster accesses val tryInfo = mutable.Map[Int, Int]() - cfg.catchNodes.foreach { cn ⇒ + cfg.catchNodes.foreach { cn => if (!tryInfo.contains(cn.startPC)) { val cnSameStartPC = cfg.catchNodes.filter(_.startPC == cn.startPC) val hasCatchFinally = cnSameStartPC.exists(_.catchType.isEmpty) @@ -318,8 +317,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { else if (cnSameStartPC.tail.isEmpty && !isThrowable) { if (cn.endPC > -1) { var end = cfg.bb(cn.endPC).successors.map { - case bb: BasicBlock ⇒ bb.startPC - 1 - case _ ⇒ -1 + case bb: BasicBlock => bb.startPC - 1 + case _ => -1 }.max if (end == -1) { end = findNextReturn(cn.handlerPC) @@ -341,8 +340,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 + case Goto(_, target) => target + case _ => cfg.code.instructions.length - 1 } val numElementsFinally = endFinally - startFinally - 1 val endOfFinally = cnSameStartPC.map(_.handlerPC).max @@ -351,8 +350,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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 _ ⇒ blockIndex + case bb: BasicBlock => bb.startPC + case _ => blockIndex }.max - 1 } } @@ -361,7 +360,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } tryInfo.map { - case (key, value) ⇒ (key, value, NestedPathType.TryCatchFinally) + case (key, value) => (key, value, NestedPathType.TryCatchFinally) }.toList } @@ -402,7 +401,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): (Path, List[(Int, Int)]) = { val path = ListBuffer[SubPath]() if (fill) { - start.to(end).foreach(i ⇒ path.append(FlatPathElement(i))) + start.to(end).foreach(i => path.append(FlatPathElement(i))) } (Path(List(NestedPathElement(path, Some(NestedPathType.Repetition)))), List((start, end))) } @@ -436,9 +435,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val popped = stack.pop() if (popped <= end) { var nextBlock = cfg.bb(popped).successors.map { - case bb: BasicBlock ⇒ bb.startPC + case bb: BasicBlock => bb.startPC // Handle Catch Nodes? - case _ ⇒ -1 + case _ => -1 }.max if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { @@ -450,7 +449,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } var containsIf = false - for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { if (cfg.code.instructions(i).isInstanceOf[If[V]]) { containsIf = true } @@ -482,7 +481,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val subPaths = ListBuffer[SubPath]() startEndPairs.foreach { - case (startSubpath, endSubpath) ⇒ + case (startSubpath, endSubpath) => val subpathElements = ListBuffer[SubPath]() if (fill) { subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) @@ -516,7 +515,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { NestedPathType.CondWithoutAlternative var previousStart = caseStmts.head - caseStmts.tail.foreach { nextStart ⇒ + caseStmts.tail.foreach { nextStart => val currentEnd = nextStart - 1 if (currentEnd >= previousStart) { startEndPairs.append((previousStart, currentEnd)) @@ -529,7 +528,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val subPaths = ListBuffer[SubPath]() startEndPairs.foreach { - case (startSubpath, endSubpath) ⇒ + case (startSubpath, endSubpath) => val subpathElements = ListBuffer[SubPath]() subPaths.append(NestedPathElement(subpathElements, None)) if (fill) { @@ -561,7 +560,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var hasFinallyBlock = false var throwableElement: Option[CatchNode] = None cfg.bb(start).successors.foreach { - case cn: CatchNode ⇒ + case cn: CatchNode => // Add once for the try block if (startEndPairs.isEmpty) { val endPC = if (cn.endPC >= 0) cn.endPC else cn.handlerPC @@ -575,7 +574,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { hasFinallyBlock = true } } - case _ ⇒ + case _ => } if (throwableElement.isDefined) { @@ -593,23 +592,23 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 + 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 } else { val endOfAfterLastCatch = cfg.bb(startEndPairs.head._2).successors.map { - case bb: BasicBlock ⇒ bb.startPC - case _ ⇒ -1 + case bb: BasicBlock => bb.startPC + case _ => -1 }.max catchBlockStartPCs.append(endOfAfterLastCatch) } catchBlockStartPCs = catchBlockStartPCs.sorted catchBlockStartPCs.zipWithIndex.foreach { - case (nextStart, i) ⇒ + case (nextStart, i) => if (i + 1 < catchBlockStartPCs.length) { startEndPairs.append( (nextStart, catchBlockStartPCs(i + 1) - 1 - numElementsFinally) @@ -622,7 +621,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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 ⇒ + 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 @@ -631,22 +630,22 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var i = indexFirstAfterCatch while (iff.isEmpty) { cfg.code.instructions(i) match { - case foundIf: If[V] ⇒ iff = Some(foundIf) - case _ ⇒ + case foundIf: If[V] => iff = Some(foundIf) + case _ => } i += 1 } indexFirstAfterCatch = iff.get.targetStmt } indexFirstAfterCatch - case _ ⇒ findNextReturn(cn.handlerPC) + case _ => findNextReturn(cn.handlerPC) } startEndPairs.append((cn.endPC, endOfCatch)) } val subPaths = ListBuffer[SubPath]() startEndPairs.foreach { - case (startSubpath, endSubpath) ⇒ + case (startSubpath, endSubpath) => val subpathElements = ListBuffer[SubPath]() subPaths.append(NestedPathElement(subpathElements, None)) if (fill) { @@ -657,7 +656,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 ⇒ + subPaths.appendAll((startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map { i => FlatPathElement(i) }) } @@ -676,7 +675,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { elementType: NestedPathType.Value ): NestedPathElement = { val outerNested = NestedPathElement(ListBuffer(), Some(elementType)) - for (_ ← 0.until(numInnerElements)) { + for (_ <- 0.until(numInnerElements)) { outerNested.element.append(NestedPathElement(ListBuffer(), None)) } outerNested @@ -698,7 +697,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 ⇒ + loops.foreach { loop => if (!belongsToLoopHeader) { if (loop.head == site) { belongsToLoopHeader = true @@ -710,7 +709,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 ⇒ + loops.foreach { nextLoop => if (!belongsToLoopHeader) { val start = nextLoop.head var end = start @@ -732,7 +731,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * 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) + 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 @@ -747,7 +746,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { protected def hasSuccessorWithAtLeastNPredecessors(bb: BasicBlock, n: Int = 2): Boolean = bb.successors.filter( _.isInstanceOf[BasicBlock] - ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { + ).foldLeft(false)((prev: Boolean, next: CFGNode) => { prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) }) @@ -766,12 +765,12 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { protected def isCondWithoutElse( branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]], - processedIfs: mutable.Map[Int, Unit.type] + processedIfs: mutable.Map[Int, Unit] ): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { - processedIfs(branchingSite) = Unit + processedIfs(branchingSite) = () return false } @@ -795,7 +794,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } val indexIf = cfg.bb(lastEle) match { - case bb: BasicBlock ⇒ + case bb: BasicBlock => val ifPos = bb.startPC.to(bb.endPC).filter( cfg.code.instructions(_).isInstanceOf[If[V]] ) @@ -804,7 +803,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } else { -1 } - case _ ⇒ -1 + case _ => -1 } if (indexIf != -1) { @@ -815,9 +814,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // very last element is a successor. If so, this represents a path past the if (or // if-elseif). var reachableCount = successors.count(_ == lastEle) - successors.foreach { next ⇒ + successors.foreach { next => val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) - val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) + val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toList: _*) while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() val to = from.successors @@ -832,7 +831,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (reachableCount > 1) { true } else { - processedIfs(branchingSite) = Unit + processedIfs(branchingSite) = () false } } @@ -853,22 +852,22 @@ 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) - seenNodes(from) = Unit + alreadySeen.foreach(seenNodes(_)= ()) + seenNodes(from) = () while (stack.nonEmpty) { val popped = stack.pop() - cfg.bb(popped).successors.foreach { nextBlock ⇒ + cfg.bb(popped).successors.foreach { nextBlock => // -1 is okay, as this value will not be processed (due to the flag processBlock) var startPC = -1 var endPC = -1 var processBlock = true nextBlock match { - case bb: BasicBlock ⇒ + case bb: BasicBlock => startPC = bb.startPC; endPC = bb.endPC - case cn: CatchNode ⇒ + case cn: CatchNode => startPC = cn.startPC; endPC = cn.endPC - case _ ⇒ processBlock = false + case _ => processBlock = false } if (processBlock) { @@ -877,7 +876,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { return true } else if (!seenNodes.contains(startPC)) { stack.push(startPC) - seenNodes(startPC) = Unit + seenNodes(startPC) = () } } } @@ -898,7 +897,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { private def getStartAndEndIndexOfLoop(index: Int): (Int, Int) = { var startIndex = -1 var endIndex = -1 - val relevantLoop = cfg.findNaturalLoops().filter(nextLoop ⇒ + 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) { @@ -923,18 +922,18 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[getStartAndEndIndexOfCondWithoutAlternative]], and [[determineTryCatchBounds]]. */ protected def processIf( - stmt: Int, processedIfs: mutable.Map[Int, Unit.type] + stmt: Int, processedIfs: mutable.Map[Int, Unit] ): CSInfo = { val csType = determineTypeOfIf(stmt, processedIfs) val (startIndex, endIndex) = csType match { - case NestedPathType.Repetition ⇒ - processedIfs(stmt) = Unit + case NestedPathType.Repetition => + processedIfs(stmt) = () getStartAndEndIndexOfLoop(stmt) - case NestedPathType.CondWithoutAlternative ⇒ + 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 _ ⇒ + case _ => getStartAndEndIndexOfCondWithAlternative(stmt, processedIfs) } (startIndex, endIndex, csType) @@ -954,7 +953,7 @@ 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 caseGotoOption = caseStmts.reverse.find { caseIndex ⇒ + val caseGotoOption = caseStmts.reverse.find { caseIndex => cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] } // If no such case is present, find the next goto after the default case @@ -987,7 +986,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * statement). */ protected def determineTypeOfIf( - stmtIndex: Int, processedIfs: mutable.Map[Int, Unit.type] + stmtIndex: Int, processedIfs: mutable.Map[Int, Unit] ): NestedPathType.Value = { // Is the first condition enough to identify loops? val loops = cfg.findNaturalLoops() @@ -1016,39 +1015,39 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // foundCS stores all found control structures as a triple in the form (start, end, type) 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]() + val processedIfs = mutable.Map[Int, Unit]() + val processedSwitches = mutable.Map[Int, Unit]() val stack = mutable.Stack[CFGNode]() - val seenCFGNodes = mutable.Map[CFGNode, Unit.type]() + val seenCFGNodes = mutable.Map[CFGNode, Unit]() - startSites.reverse.foreach { site ⇒ + startSites.reverse.foreach { site => stack.push(cfg.bb(site)) - seenCFGNodes(cfg.bb(site)) = Unit + seenCFGNodes(cfg.bb(site)) = () } while (stack.nonEmpty) { val next = stack.pop() - seenCFGNodes(next) = Unit + seenCFGNodes(next) = () next match { - case bb: BasicBlock ⇒ - for (i ← bb.startPC.to(bb.endPC)) { + case bb: BasicBlock => + for (i <- bb.startPC.to(bb.endPC)) { cfg.code.instructions(i) match { - case _: If[V] if !processedIfs.contains(i) ⇒ + case _: If[V] if !processedIfs.contains(i) => foundCS.append(processIf(i, processedIfs)) - processedIfs(i) = Unit - case _: Switch[V] if !processedSwitches.contains(i) ⇒ + processedIfs(i) = () + case _: Switch[V] if !processedSwitches.contains(i) => foundCS.append(processSwitch(i)) - processedSwitches(i) = Unit - case _ ⇒ + processedSwitches(i) = () + case _ => } } - case _ ⇒ + case _ => } if (next.nodeId == endSite) { val doesPathExist = stack.filter(_.nodeId >= 0).foldLeft(false) { - (doesExist: Boolean, next: CFGNode) ⇒ + (doesExist: Boolean, next: CFGNode) => doesExist || doesPathExistTo(next.nodeId, endSite) } // In case no more path exists, clear the stack which (=> no more iterations) @@ -1064,7 +1063,7 @@ 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, _) ⇒ + case (start, end, _) => (startSites.forall(start > _) && endSite < start) || (startSites.forall(_ < start) && startSites.forall(_ > end)) } @@ -1074,21 +1073,21 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var relevantTryCatchBlocks = determineTryCatchBounds() // Filter out all blocks that completely surround the given start and end sites relevantTryCatchBlocks = relevantTryCatchBlocks.filter { - case (tryStart, tryEnd, _) ⇒ + case (tryStart, tryEnd, _) => val tryCatchParts = buildTryCatchPath(tryStart, tryEnd, fill = false) !tryCatchParts._2.exists { - case (nextInnerStart, nextInnerEnd) ⇒ + 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, _, _) ⇒ + case (tryStart, _, _) => startSites.exists(tryStart >= _) && tryStart <= endSite } foundCS.appendAll(relevantTryCatchBlocks) - foundCS.sortBy { case (start, _, _) ⇒ start }.toList + foundCS.sortBy { case (start, _, _) => start }.toList } /** @@ -1107,13 +1106,13 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { buildPathForSwitch(start, end, fill) } else { element._3 match { - case NestedPathType.Repetition ⇒ + case NestedPathType.Repetition => buildRepetitionPath(start, end, fill) - case NestedPathType.CondWithAlternative ⇒ + case NestedPathType.CondWithAlternative => buildCondPath(start, end, NestedPathType.CondWithAlternative, fill) - case NestedPathType.CondWithoutAlternative ⇒ + case NestedPathType.CondWithoutAlternative => buildCondPath(start, end, NestedPathType.CondWithoutAlternative, fill) - case NestedPathType.TryCatchFinally ⇒ + case NestedPathType.TryCatchFinally => buildTryCatchPath(start, end, fill) } } @@ -1141,7 +1140,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 ⇒ + 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 @@ -1163,7 +1162,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 } + childrenOf.foreach { nextCS => mapChildrenOf(nextCS._1) = nextCS._2 } HierarchicalCSOrder(List(( None, cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf)) @@ -1194,10 +1193,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var indexLastCSEnd = startIndex // Recursively transform the hierarchies to paths - topElements.foreach { nextTopEle ⇒ + topElements.foreach { nextTopEle => // Build path up to the next control structure val nextCSStart = nextTopEle.hierarchy.head._1.get._1 - indexLastCSEnd.until(nextCSStart).foreach { i ⇒ + indexLastCSEnd.until(nextCSStart).foreach { i => finalPath.append(FlatPathElement(i)) } @@ -1220,7 +1219,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val isRepElement = npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == NestedPathType.Repetition var lastInsertedIndex = 0 - childrenPath.elements.foreach { nextEle ⇒ + childrenPath.elements.foreach { nextEle => if (isRepElement) { npe.element.append(nextEle) } else { @@ -1232,10 +1231,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } lastInsertedIndex = nextEle match { - case fpe: FlatPathElement ⇒ fpe.element - case inner: NestedPathElement ⇒ Path.getLastElementInNPE(inner).element + case fpe: FlatPathElement => fpe.element + case inner: NestedPathElement => Path.getLastElementInNPE(inner).element // Compiler wants it but should never be the case! - case _ ⇒ -1 + case _ => -1 } if (insertIndex < startEndPairs.length && lastInsertedIndex >= startEndPairs(insertIndex)._2) { @@ -1255,7 +1254,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { insertPos.element.appendAll(currentToInsert) insertIndex += 1 // Fill the rest NPEs if necessary - insertIndex.until(startEndPairs.length).foreach { i ⇒ + insertIndex.until(startEndPairs.length).foreach { i => insertPos = npe.element(i).asInstanceOf[NestedPathElement] insertPos.element.appendAll( startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement) @@ -1267,8 +1266,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val subPathNpe = subpath.elements.head.asInstanceOf[NestedPathElement] val subPathToAdd = NestedPathElement( subPathNpe.element.filter { - case npe: NestedPathElement ⇒ npe.element.nonEmpty - case _ ⇒ true + case npe: NestedPathElement => npe.element.nonEmpty + case _ => true }, subPathNpe.elementType ) finalPath.append(subPathToAdd) 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 45d98401b8..126938b55e 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 @@ -87,7 +87,7 @@ case class Path(elements: List[SubPath]) { obj: DUVar[ValueInformation], stmts: Array[Stmt[V]] ): List[Int] = { val defAndUses = ListBuffer[Int]() - val stack = mutable.Stack[Int](obj.definedBy.toArray: _*) + val stack = mutable.Stack[Int](obj.definedBy.toList: _*) while (stack.nonEmpty) { val popped = stack.pop() @@ -95,14 +95,14 @@ case class Path(elements: List[SubPath]) { defAndUses.append(popped) stmts(popped) match { - case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ + case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] => 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] ⇒ + case a: Assignment[V] if a.expr.isInstanceOf[New] => stack.pushAll(a.targetVar.usedBy.toArray) - case _ ⇒ + case _ => } } } @@ -116,12 +116,12 @@ case class Path(elements: List[SubPath]) { * [[NestedPathElement]]s. */ private def containsPathElement(subpath: NestedPathElement, element: Int): Boolean = { - subpath.element.foldLeft(false) { (old: Boolean, nextSubpath: SubPath) ⇒ + subpath.element.foldLeft(false) { (old: Boolean, nextSubpath: SubPath) => old || (nextSubpath match { - case fpe: FlatPathElement ⇒ fpe.element == element - case npe: NestedPathElement ⇒ containsPathElement(npe, element) + 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 + case _ => false }) } } @@ -133,11 +133,11 @@ case class Path(elements: List[SubPath]) { private def removeOuterBranching(npe: NestedPathElement): ListBuffer[SubPath] = { if (npe.element.tail.isEmpty) { npe.element.head match { - case innerNpe: NestedPathElement ⇒ removeOuterBranching(innerNpe) - case fpe: SubPath ⇒ ListBuffer[SubPath](fpe) + case innerNpe: NestedPathElement => removeOuterBranching(innerNpe) + case fpe: SubPath => ListBuffer[SubPath](fpe) } } else { - ListBuffer[SubPath](npe.element: _*) + npe.element } } @@ -152,7 +152,7 @@ case class Path(elements: List[SubPath]) { npe: NestedPathElement, endSite: Int ): NestedPathElement = { npe.element.foreach { - case innerNpe: NestedPathElement ⇒ + case innerNpe: NestedPathElement => if (innerNpe.elementType.isEmpty) { if (!containsPathElement(innerNpe, endSite)) { innerNpe.element.clear() @@ -160,7 +160,7 @@ case class Path(elements: List[SubPath]) { } else { stripUnnecessaryBranches(innerNpe, endSite) } - case _ ⇒ + case _ => } npe } @@ -189,9 +189,9 @@ case class Path(elements: List[SubPath]) { */ private def makeLeanPathAcc( toProcess: NestedPathElement, - siteMap: Map[Int, Unit.type], + siteMap: Map[Int, Unit], endSite: Int, - includeAlternatives: Boolean = false + includeAlternatives: Boolean = false ): (Option[NestedPathElement], Boolean) = { val elements = ListBuffer[SubPath]() var stop = false @@ -199,12 +199,12 @@ case class Path(elements: List[SubPath]) { val isTryCatch = includeAlternatives || (toProcess.elementType.isDefined && toProcess.elementType.get == NestedPathType.TryCatchFinally) - toProcess.element.foreach { next ⇒ + 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 ⇒ + case fpe: FlatPathElement if !hasTargetBeenSeen => if (siteMap.contains(fpe.element) && !hasTargetBeenSeen) { elements.append(fpe.copy()) } @@ -212,14 +212,14 @@ case class Path(elements: List[SubPath]) { hasTargetBeenSeen = true stop = true } - case npe: NestedPathElement if isTryCatch ⇒ + case npe: NestedPathElement if isTryCatch => val (leanedSubPath, _) = makeLeanPathAcc( npe, siteMap, endSite, includeAlternatives = true ) if (leanedSubPath.isDefined) { elements.append(leanedSubPath.get) } - case npe: NestedPathElement ⇒ + case npe: NestedPathElement => if (!hasTargetBeenSeen) { val (leanedSubPath, wasTargetSeen) = makeLeanPathAcc( npe, siteMap, endSite @@ -231,7 +231,7 @@ case class Path(elements: List[SubPath]) { hasTargetBeenSeen = true } } - case _ ⇒ + case _ => } } } @@ -264,30 +264,30 @@ case class Path(elements: List[SubPath]) { def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Path = { 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 ⇒ + val siteMap = getAllDefAndUseSites(obj, stmts).filter { nextSite => stmts(nextSite) match { - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + case Assignment(_, _, expr: VirtualFunctionCall[V]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) - case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + case ExprStmt(_, expr: VirtualFunctionCall[V]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) - case _ ⇒ true + case _ => true } - }.map { s ⇒ (s, Unit) }.toMap + }.map { s => (s, ()) }.toMap var leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.toArray.max var reachedEndSite = false - elements.foreach { next ⇒ + elements.foreach { next => if (!reachedEndSite) { next match { - case fpe: FlatPathElement if siteMap.contains(fpe.element) ⇒ + case fpe: FlatPathElement if siteMap.contains(fpe.element) => leanPath.append(fpe) if (fpe.element == endSite) { reachedEndSite = true } - case npe: NestedPathElement ⇒ + case npe: NestedPathElement => val (leanedPath, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) if (npe.elementType.isDefined && npe.elementType.get != NestedPathType.TryCatchFinally) { @@ -296,7 +296,7 @@ case class Path(elements: List[SubPath]) { if (leanedPath.isDefined) { leanPath.append(leanedPath.get) } - case _ ⇒ + case _ => } } } @@ -307,20 +307,20 @@ case class Path(elements: List[SubPath]) { if (leanPath.tail.isEmpty) { leanPath.head match { case npe: NestedPathElement if npe.elementType.get == NestedPathType.Repetition || - npe.element.tail.isEmpty ⇒ + npe.element.tail.isEmpty => leanPath = removeOuterBranching(npe) - case _ ⇒ + 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) ⇒ + (npe.elementType.get != NestedPathType.TryCatchFinally) => val newLast = stripUnnecessaryBranches(npe, endSite) leanPath.remove(leanPath.size - 1) leanPath.append(newLast) - case _ ⇒ + case _ => } } @@ -336,14 +336,14 @@ object Path { */ def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { npe.element.last match { - case fpe: FlatPathElement ⇒ fpe - case npe: NestedPathElement ⇒ + case fpe: FlatPathElement => fpe + case npe: NestedPathElement => npe.element.last match { - case fpe: FlatPathElement ⇒ fpe - case innerNpe: NestedPathElement ⇒ getLastElementInNPE(innerNpe) - case _ ⇒ FlatPathElement(-1) + case fpe: FlatPathElement => fpe + case innerNpe: NestedPathElement => getLastElementInNPE(innerNpe) + case _ => FlatPathElement(-1) } - case _ ⇒ FlatPathElement(-1) + case _ => FlatPathElement(-1) } } 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 ddddbe3be0..7e5fe1655a 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 @@ -35,7 +35,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { subpath: SubPath, fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] ): Option[StringTree] = { subpath match { - case fpe: FlatPathElement ⇒ + case fpe: FlatPathElement => val sci = if (fpe2Sci.contains(fpe.element)) { StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { @@ -49,9 +49,9 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { StringConstancyInformation.lb } else { r.asInterim.ub match { - case property: StringConstancyProperty ⇒ + case property: StringConstancyProperty => property.stringConstancyInformation - case _ ⇒ + case _ => StringConstancyInformation.lb } } @@ -64,22 +64,22 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } else { Some(StringTreeConst(sci)) } - case npe: NestedPathElement ⇒ + case npe: NestedPathElement => if (npe.elementType.isDefined) { npe.elementType.get match { - case NestedPathType.Repetition ⇒ + case NestedPathType.Repetition => val processedSubPath = pathToStringTree( Path(npe.element.toList), fpe2Sci, resetExprHandler = false ) Some(StringTreeRepetition(processedSubPath)) - case _ ⇒ - val processedSubPaths = npe.element.map { ne ⇒ + case _ => + val processedSubPaths = npe.element.map { ne => pathToTreeAcc(ne, fpe2Sci) }.filter(_.isDefined).map(_.get) if (processedSubPaths.nonEmpty) { npe.elementType.get match { case NestedPathType.CondWithAlternative | - NestedPathType.TryCatchFinally ⇒ + NestedPathType.TryCatchFinally => // In case there is only one element in the sub path, // transform it into a conditional element (as there is no // alternative) @@ -88,9 +88,9 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } else { Some(StringTreeCond(processedSubPaths)) } - case NestedPathType.CondWithoutAlternative ⇒ + case NestedPathType.CondWithoutAlternative => Some(StringTreeCond(processedSubPaths)) - case _ ⇒ None + case _ => None } } else { None @@ -98,10 +98,10 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } } else { npe.element.size match { - case 0 ⇒ None - case 1 ⇒ pathToTreeAcc(npe.element.head, fpe2Sci) - case _ ⇒ - val processed = npe.element.map { ne ⇒ + case 0 => None + case 1 => pathToTreeAcc(npe.element.head, fpe2Sci) + case _ => + val processed = npe.element.map { ne => pathToTreeAcc(ne, fpe2Sci) }.filter(_.isDefined).map(_.get) if (processed.isEmpty) { @@ -111,7 +111,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } } } - case _ ⇒ None + case _ => None } } @@ -145,16 +145,16 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { resetExprHandler: Boolean = true ): StringTree = { val tree = path.elements.size match { - case 1 ⇒ + 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 ⇒ + case _ => + val concatElement = StringTreeConcat(ListBuffer.from(path.elements.map { ne => pathToTreeAcc(ne, fpe2Sci) - }.filter(_.isDefined).map(_.get).to[ListBuffer]) + }.filter(_.isDefined).map(_.get))) // 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) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index a20cda7b3a..4247de2a22 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -42,17 +42,17 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde var nextStmt = startSites.min while (nextStmt >= 0 && startSite.isEmpty) { cfg.code.instructions(nextStmt) match { - case iff: If[V] if startSites.contains(iff.targetStmt) ⇒ + case iff: If[V] if startSites.contains(iff.targetStmt) => startSite = Some(nextStmt) - case _: Switch[V] ⇒ + case _: Switch[V] => val (startSwitch, endSwitch, _) = processSwitch(nextStmt) val isParentSwitch = startSites.forall { - nextStartSite ⇒ nextStartSite >= startSwitch && nextStartSite <= endSwitch + nextStartSite => nextStartSite >= startSwitch && nextStartSite <= endSwitch } if (isParentSwitch) { startSite = Some(nextStmt) } - case _ ⇒ + case _ => } nextStmt -= 1 } 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 46b1279ebd..7db2d927dd 100644 --- a/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala +++ b/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala @@ -44,64 +44,64 @@ class TACAITest extends AnyFunSpec with Matchers { for { Seq(_, _, className, methodName, test, domainName) <- tests } { 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) - 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) + // 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( - "expected is longer than actual - first line: "+expectedIt.next()+ - "\n computed representation:\n"+actual.mkString("\n") + 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( + "expected is longer than actual - first line: "+expectedIt.next()+ + "\n computed representation:\n"+actual.mkString("\n") + ) + } } } } From 9328f627353961221dc467f1013d4d604499d98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 3 Feb 2023 13:09:47 +0100 Subject: [PATCH 318/583] Adress some review comments --- .../string_definition/StringTree.scala | 28 +- .../org/opalj/fpcf/par/EPKState.scala.temp | 260 ------------------ .../InterproceduralComputationState.scala | 3 +- .../InterpretationHandler.scala | 5 +- 4 files changed, 17 insertions(+), 279 deletions(-) delete mode 100644 OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp 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 0015bad32d..b23c32ef1c 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 @@ -228,14 +228,10 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * 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]) + + val repetitionElements = newChildren.collect { case str: StringTreeRepetition => str } + // 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 @@ -243,20 +239,22 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme subtree.children.appendAll(newChildren) subtree } else { - val childrenOfReps = repetitionElements.map( - _.asInstanceOf[StringTreeRepetition].child - ) + val childrenOfReps = repetitionElements.map(_.child) val newRepElement = StringTreeRepetition(StringTreeOr(childrenOfReps)) val indexFirstChild = newChildren.indexOf(repetitionElements.head) - newChildren = newChildren.filterNot(_.isInstanceOf[StringTreeRepetition]) + + newChildren = newChildren.filter { + case _: StringTreeRepetition => false + case _ => true + } + newChildren.insert(indexFirstChild, newRepElement) if (newChildren.length == 1) { newChildren.head } else { - if (subtree.isInstanceOf[StringTreeOr]) { - StringTreeOr(newChildren) - } else { - StringTreeConcat(newChildren) + subtree match { + case _: StringTreeOr => StringTreeOr(newChildren) + case _ => StringTreeConcat(newChildren) } } } 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 deleted file mode 100644 index e6e813e148..0000000000 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp +++ /dev/null @@ -1,260 +0,0 @@ -/* 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/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 4f7731473a..2197432510 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,7 +8,8 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.value.ValueInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.{DeclaredMethod, Method} +import org.opalj.br.DeclaredMethod +import org.opalj.br.Method import org.opalj.tac.fpcf.properties.cg.Callees import org.opalj.tac.fpcf.properties.cg.Callers import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path 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 23b5b9064c..43a3d41e18 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 @@ -90,10 +90,9 @@ object InterpretationHandler { */ def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { - case VirtualFunctionCall(_, clazz, _, name, _, _, _) => + case VirtualFunctionCall(_, clazz, _, "toString", _, _, _) => val className = clazz.mostPreciseObjectType.fqn - (className == "java/lang/StringBuilder" || className == "java/lang/StringBuffer") && - name == "toString" + (className == "java/lang/StringBuilder" || className == "java/lang/StringBuffer") case _ => false } From 01802a9978280207b1cda13f55465f74567b799c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Mon, 6 Feb 2023 16:07:06 +0100 Subject: [PATCH 319/583] More fixes to review comments --- .../info/StringAnalysisReflectiveCalls.scala | 88 ++++---- .../string_analysis/LocalTestMethods.java | 19 -- .../InterproceduralStringAnalysis.scala | 193 ++++++++---------- ...cala => ArrayPreparationInterpreter.scala} | 6 +- ...InterproceduralInterpretationHandler.scala | 2 +- .../finalizer/ArrayLoadFinalizer.scala | 4 +- .../string_analysis/preprocessing/Path.scala | 2 +- 7 files changed, 148 insertions(+), 166 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/{ArrayLoadPreparer.scala => ArrayPreparationInterpreter.scala} (97%) 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 f3a6c5d402..29740822a9 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 @@ -2,12 +2,9 @@ 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.FinalP import org.opalj.fpcf.InterimELUBP import org.opalj.fpcf.InterimLUBP @@ -16,7 +13,6 @@ 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.Project import org.opalj.br.analyses.ReportableAnalysisResult @@ -35,15 +31,10 @@ import org.opalj.tac.Call import org.opalj.tac.ExprStmt 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.string_analysis.{LazyInterproceduralStringAnalysis, LazyIntraproceduralStringAnalysis, P, V} 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.cg.RTACallGraphKey -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 @@ -65,6 +56,35 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] + private val relevantCryptoMethodNames = List( + "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" + ) + + private val relevantReflectionMethodNames = 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" + ) + + private var includeCrypto = false + + /** + * Retrieves all relevant method names, i.e., those methods from the Reflection API that have at least one string + * argument and shall be considered by this analysis. The string are supposed to have the format as produced + * by [[buildFQMethodName]]. If the 'crypto' parameter is set, relevant methods of the javax.crypto API are + * included, too. + */ + private def relevantMethodNames = if(includeCrypto) + relevantReflectionMethodNames ++ relevantCryptoMethodNames + else + relevantReflectionMethodNames + /** * 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, @@ -72,26 +92,6 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { */ private val entityContext = 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. The string are supposed to have the format as produced by [[buildFQMethodName]]. - */ - private val relevantMethodNames = List( - // 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 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 an * analysis crash (e.g., com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize) @@ -158,7 +158,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { ps: PropertyStore, method: Method, call: Call[V], resultMap: ResultMapType ): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { - val fqnMethodName = s"${method.classFile.thisType.fqn}#${method.name}" + val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) if (!ignoreMethods.contains(fqnMethodName)) { if (executionCounter >= executeFrom && executionCounter <= executeTo) { println( @@ -249,31 +249,47 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } } + override def checkAnalysisSpecificParameters(parameters: Seq[String]): Seq[String] = { + + parameters.flatMap{ p => p.toLowerCase match { + case "-includecryptoapi" => Nil + case "-intraprocedural" => Nil + case _ => List(s"Unknown parameter: $p") + }} + + } + override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { + + // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. + includeCrypto = parameters.exists( p => p.equalsIgnoreCase("-includeCryptoApi")) + // Check whether intraprocedural analysis should be run. By default, interprocedural is selected. + val runIntraproceduralAnalysis = parameters.exists(p => p.equalsIgnoreCase("-intraprocedural")) + + val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) + implicit val (propertyStore, analyses) = manager.runAll( - LazyInterproceduralStringAnalysis - // LazyIntraproceduralStringAnalysis + if(runIntraproceduralAnalysis) LazyIntraproceduralStringAnalysis else LazyInterproceduralStringAnalysis ) // Stores the obtained results for each supported reflective operation - val resultMap: ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]]() + val resultMap = mutable.Map[String, ListBuffer[StringConstancyInformation]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } project.allMethodsWithBody.foreach { m => // To dramatically reduce work, quickly check if a method is relevant at all if (instructionsContainRelevantMethod(m.body.get.instructions)) { - 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 + val tac = tacaiEOptP.ub.tac.get processStatements(propertyStore, tac.stmts, m, resultMap) } } else { 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 30c2c23530..72a90da283 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 @@ -981,25 +981,6 @@ public void unknownCharValue() { 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()); - // } - 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 24cefdd022..ec24f3aa49 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 @@ -45,7 +45,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.ArrayLoadPreparer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr import org.opalj.tac.fpcf.analyses.cg.{RTATypeIterator, TypeIterator} @@ -169,12 +169,13 @@ class InterproceduralStringAnalysis( ): ProperPropertyComputationResult = { val uvar = state.entity._1 val defSites = uvar.definedBy.toArray.sorted - val stmts = state.tac.stmts if (state.tac == null || state.callees == null) { return getInterimResult(state) } + val stmts = state.tac.stmts + if (state.computedLeanPath == null) { state.computedLeanPath = computeLeanPath(uvar, state.tac) } @@ -332,112 +333,96 @@ class InterproceduralStringAnalysis( state: InterproceduralComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != 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) - case _ => - state.dependees = eps :: state.dependees - getInterimResult(state) + + eps match { + case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => + // 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 } - 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) + determinePossibleStrings(state) + case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => + state.callees = callees + if (state.dependees.isEmpty) { + determinePossibleStrings(state) + } else { + getInterimResult(state) } - case Callers.key => eps match { - case FinalP(callers: Callers) => - state.callers = callers - if (state.dependees.isEmpty) { - registerParams(state) - determinePossibleStrings(state) - } else { - getInterimResult(state) - } - case _ => - state.dependees = eps :: state.dependees - getInterimResult(state) + case FinalP(callers: Callers) if eps.pk.equals(Callers.key) => + state.callers = callers + if (state.dependees.isEmpty) { + registerParams(state) + determinePossibleStrings(state) + } else { + getInterimResult(state) + } + case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + 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)) { + val pos = state.paramResultPositions(e) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(e) + state.parameterDependeesCount -= 1 } - case StringConstancyProperty.key => - 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)) { - val pos = state.paramResultPositions(e) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(e) - state.parameterDependeesCount -= 1 - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(e)) { - state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( - _, p.stringConstancyInformation - )) - // Update the state - 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(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 - 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(_: StringConstancyProperty, ub: StringConstancyProperty) => - state.dependees = eps :: state.dependees - val uvar = eps.e.asInstanceOf[P]._1 - state.var2IndexMapping(uvar).foreach { i => - state.appendToInterimFpe2Sci( - i, ub.stringConstancyInformation, Some(uvar) - ) - } - getInterimResult(state) - case _ => - state.dependees = eps :: state.dependees - getInterimResult(state) + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(e)) { + state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( + _, p.stringConstancyInformation + )) + // Update the state + 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(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 + 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(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + state.dependees = eps :: state.dependees + val uvar = eps.e.asInstanceOf[P]._1 + state.var2IndexMapping(uvar).foreach { i => + state.appendToInterimFpe2Sci( + i, ub.stringConstancyInformation, Some(uvar) + ) + } + getInterimResult(state) + case _ => + state.dependees = eps :: state.dependees + getInterimResult(state) + } } @@ -675,7 +660,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] => - ArrayLoadPreparer.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) + 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) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.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/interprocedural/ArrayLoadPreparer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 7e85200237..8bce835fc7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -31,7 +31,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationSta * * @author Patrick Mell */ -class ArrayLoadPreparer( +class ArrayPreparationInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, state: InterproceduralComputationState, @@ -54,7 +54,7 @@ class ArrayLoadPreparer( val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() val defSites = instr.arrayRef.asVar.definedBy.toArray - val allDefSites = ArrayLoadPreparer.getStoreAndLoadDefSites( + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( instr, state.tac.stmts ) @@ -103,7 +103,7 @@ class ArrayLoadPreparer( } -object ArrayLoadPreparer { +object ArrayPreparationInterpreter { 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 d38e5d30c3..24be2f8fd4 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 @@ -162,7 +162,7 @@ class InterproceduralInterpretationHandler( private def processArrayLoad( expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new ArrayLoadPreparer( + val r = new ArrayPreparationInterpreter( cfg, this, state, params ).interpret(expr, defSite) val sci = if (r.isFinal) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 8c2b055144..d0bdfa7f88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -9,7 +9,7 @@ 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.ArrayLoadPreparer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** @@ -27,7 +27,7 @@ class ArrayLoadFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = ArrayLoadPreparer.getStoreAndLoadDefSites( + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( instr, state.tac.stmts ) 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 126938b55e..cf9d2a3d8e 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 @@ -32,7 +32,7 @@ case class FlatPathElement(element: Int) extends SubPath /** * Identifies the nature of a nested path element. */ -object NestedPathType extends Enumeration { +case object NestedPathType extends Enumeration { /** * Used to mark any sort of loops. From 057a853379d8d2271f48683f5e94a917342957c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 17 Feb 2023 10:54:02 +0100 Subject: [PATCH 320/583] Address TODOs in CFG. Fix comment in IntraproceduralStringAnalysis --- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 26 ++++++++++++++++--- .../IntraproceduralStringAnalysis.scala | 2 +- 2 files changed, 24 insertions(+), 4 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 b554c99d9c..7f2bba7f9f 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 @@ -805,15 +805,35 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( * @see [[PostDominatorTree.apply]] */ def postDominatorTree: PostDominatorTree = { + // Collect Indices of all nodes that lead into an artificial ExitNode val exitNodes = basicBlocks.zipWithIndex.filter { next => next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] }.map(_._2) + + // If there is one unique such node, we have a 'uniqueExitNode', otherwise not + val uniqueExitNode = if(exitNodes.length == 1) Some(exitNodes.head) else None + + // Additional exit nodes are empty if there is only one exit node. Otherwise, they are collected as all nodes + // leading into artificial ExitNodes that are NOT the "normalReturnNode", i.e. abnormal termination. + // TODO: Verify this is intended behavior for PostDominatorTrees + val additionalExitNodes = if(uniqueExitNode.isDefined) EmptyIntTrieSet else { + IntTrieSet( + basicBlocks + .zipWithIndex + .filter { next => + next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] && !next._1.successors.head.equals(normalReturnNode) + } + .map(_._2) + ) + } + + // All nodes that have an ExitNode successor which is NOT the normalReturnNode are "additionalExitNodes" PostDominatorTree( - if (exitNodes.length == 1) Some(exitNodes.head) else None, + uniqueExitNode, i => exitNodes.contains(i), - // TODO: Pass an IntTrieSet if exitNodes contains more than one element - EmptyIntTrieSet, + additionalExitNodes, // TODO: Correct function (just copied it from one of the tests)? + // TODO: Function seems correct to me - verify! (f: Int => Unit) => exitNodes.foreach(e => f(e)), foreachSuccessor, foreachPredecessor, 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 4b60b503b5..85278b5f12 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 @@ -43,7 +43,7 @@ import org.opalj.tac.TACode * 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 + * This analysis takes into account only the enclosing function as a context, i.e., it is * intraprocedural. Values coming from other functions are regarded as dynamic values even if the * function returns a constant string value. *

    From e20b0a71714c63a79dc9c9f51d56a8b4b048711a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 17 Feb 2023 11:02:16 +0100 Subject: [PATCH 321/583] Remove unecessary type alias, rename StringTreeElement to StringTree --- .../string_definition/StringTree.scala | 41 +++++++++---------- .../string_definition/properties.scala | 14 ------- .../preprocessing/PathTransformer.scala | 6 +-- 3 files changed, 23 insertions(+), 38 deletions(-) delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala 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 b23c32ef1c..bc7a6e175d 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 @@ -4,15 +4,14 @@ 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 [[org.opalj.br.fpcf.properties.properties.StringTree]]s. + * Super type for modeling nodes and leafs of [[org.opalj.br.fpcf.properties.string_definition.StringTree]]s. * * @author Patrick Mell */ -sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeElement]) { +sealed abstract class StringTree(val children: ListBuffer[StringTree]) { /** * This is a helper function which processes the `reduce` operation for [[StringTreeOr]] and @@ -22,7 +21,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * is passed). */ private def processReduceCondOrReduceOr( - children: List[StringTreeElement], processOr: Boolean = true + children: List[StringTree], processOr: Boolean = true ): List[StringConstancyInformation] = { val reduced = children.flatMap(reduceAcc) val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) @@ -67,7 +66,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * elements. */ private def processReduceConcat( - children: List[StringTreeElement] + children: List[StringTree] ): List[StringConstancyInformation] = { val reducedLists = children.map(reduceAcc) // Stores whether we deal with a flat structure or with a nested structure (in the latter @@ -135,7 +134,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * 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] = { + private def reduceAcc(subtree: StringTree): List[StringConstancyInformation] = { subtree match { case StringTreeRepetition(c, lowerBound, upperBound) => val times = if (lowerBound.isDefined && upperBound.isDefined) @@ -160,13 +159,13 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * 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 [[StringTreeElement]] with unique elements. + * @return Returns a list of [[StringTree]] with unique elements. */ private def removeDuplicateTreeValues( - children: ListBuffer[StringTreeElement] - ): ListBuffer[StringTreeElement] = { + children: ListBuffer[StringTree] + ): ListBuffer[StringTree] = { val seen = mutable.Map[StringConstancyInformation, Boolean]() - val unique = ListBuffer[StringTreeElement]() + val unique = ListBuffer[StringTree]() children.foreach { case next @ StringTreeConst(sci) => if (!seen.contains(sci)) { @@ -331,10 +330,10 @@ 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 -) extends StringTreeElement(ListBuffer(child)) + var child: StringTree, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None +) extends StringTree(ListBuffer(child)) /** * [[StringTreeConcat]] models the concatenation of multiple strings. For example, if it is known @@ -343,8 +342,8 @@ case class StringTreeRepetition( * represents ''s_1'' and the last child / last element ''s_n''. */ case class StringTreeConcat( - override val children: ListBuffer[StringTreeElement] -) extends StringTreeElement(children) + override val children: ListBuffer[StringTree] +) extends StringTree(children) /** * [[StringTreeOr]] models that a string (or part of a string) has one out of several possible @@ -356,8 +355,8 @@ case class StringTreeConcat( * a (sub) string. */ case class StringTreeOr( - override val children: ListBuffer[StringTreeElement] -) extends StringTreeElement(children) + override val children: ListBuffer[StringTree] +) extends StringTree(children) /** * [[StringTreeCond]] is used to model that a string (or part of a string) is optional / may @@ -368,8 +367,8 @@ case class StringTreeOr( * string may have (contain) a particular but not necessarily. */ case class StringTreeCond( - override val children: ListBuffer[StringTreeElement] -) extends StringTreeElement(children) + override val children: ListBuffer[StringTree] +) extends StringTree(children) /** * [[StringTreeConst]]s are the only elements which are supposed to act as leafs within a @@ -380,4 +379,4 @@ case class StringTreeCond( */ case class StringTreeConst( sci: StringConstancyInformation -) extends StringTreeElement(ListBuffer()) +) extends StringTree(ListBuffer()) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala deleted file mode 100644 index df9a18d40a..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala +++ /dev/null @@ -1,14 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties - -import org.opalj.br.fpcf.properties.string_definition.StringTreeElement - -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 = StringTreeElement - -} 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 7e5fe1655a..3ad7d69363 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,8 +4,8 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map -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.StringTree 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 @@ -16,7 +16,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation /** * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such - * as [[org.opalj.br.fpcf.properties.properties.StringTree]]s for example. + * as [[org.opalj.br.fpcf.properties.string_definition.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?`. @@ -135,7 +135,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * [[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 + * [[org.opalj.br.fpcf.properties.string_definition.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. */ From 1657d90119c7688feafacfc2c36a216f8a2c5729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 17 Feb 2023 11:22:04 +0100 Subject: [PATCH 322/583] Add inline annotations, early return for instructions filter in StringAnalysisReflectiveCalls, retrieve TAC from property store in IntraproceduralStringAnalysis --- .../info/StringAnalysisReflectiveCalls.scala | 30 +++++++++++-------- .../IntraproceduralStringAnalysis.scala | 25 +++++++--------- 2 files changed, 29 insertions(+), 26 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 29740822a9..dfd19133e5 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 @@ -118,7 +118,8 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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 = + @inline + private final def buildFQMethodName(declaringClass: ReferenceType, methodName: String): String = s"${declaringClass.toJava}#$methodName" /** @@ -128,7 +129,8 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * @note Internally, this method makes use of [[relevantMethodNames]]. A method can only be * relevant if it occurs in [[relevantMethodNames]]. */ - private def isRelevantCall(declaringClass: ReferenceType, methodName: String): Boolean = + @inline + private final def isRelevantCall(declaringClass: ReferenceType, methodName: String): Boolean = relevantMethodNames.contains(buildFQMethodName(declaringClass, methodName)) /** @@ -136,17 +138,21 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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, _) = nextInstr - isRelevantCall(declClass, methodName) - case INVOKEVIRTUAL.opcode => - val INVOKEVIRTUAL(declClass, methodName, _) = nextInstr - isRelevantCall(declClass, methodName) - case _ => false - }) + + instructions + .filter(_ != null) + .foreach{ instr => + (instr.opcode: @switch) match { + case INVOKESTATIC.opcode => + val INVOKESTATIC(declClass, _, methodName, _) = instr + if (isRelevantCall(declClass, methodName)) return true + case INVOKEVIRTUAL.opcode => + val INVOKEVIRTUAL(declClass, methodName, _) = instr + if (isRelevantCall(declClass, methodName)) return true + } } + + 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 85278b5f12..b961fc138d 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 @@ -87,20 +87,17 @@ class IntraproceduralStringAnalysis( // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val tacProvider = p.get(EagerDetachedTACAIKey) - 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 - // } - // } + // Retrieve TAC from 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 961f7065d06c2dc7cdff643cd2662d0cee452b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 Nov 2023 17:23:27 +0100 Subject: [PATCH 323/583] Fix caller and callee definitions --- .../string_analysis/InterproceduralComputationState.scala | 4 ++-- .../string_analysis/InterproceduralStringAnalysis.scala | 4 ++-- .../string_analysis/IntraproceduralStringAnalysis.scala | 2 +- .../interpretation/AbstractStringInterpreter.scala | 2 +- .../InterproceduralInterpretationHandler.scala | 2 +- .../InterproceduralVirtualMethodCallInterpreter.scala | 2 +- 6 files changed, 8 insertions(+), 8 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 2197432510..62a36ae116 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 @@ -10,8 +10,8 @@ import org.opalj.value.ValueInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.DeclaredMethod import org.opalj.br.Method -import org.opalj.tac.fpcf.properties.cg.Callees -import org.opalj.tac.fpcf.properties.cg.Callers +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 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 ec24f3aa49..d01b0336b9 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 @@ -22,8 +22,8 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.FieldType -import org.opalj.tac.fpcf.properties.cg.Callers -import org.opalj.tac.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder 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 b961fc138d..d36a394a3e 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,7 +20,7 @@ import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.tac.fpcf.properties.cg.Callees +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 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 8368815a99..2ead658640 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,7 +12,7 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.{NoContext, StringConstancyProperty} -import org.opalj.tac.fpcf.properties.cg.Callees +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/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 24be2f8fd4..0b9a976fb5 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 @@ -11,7 +11,7 @@ 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.tac.fpcf.properties.cg.Callees +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 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 3c17e653f3..67876bffdb 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 @@ -9,7 +9,7 @@ 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.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall From a7abacbfbdc443c9df3cf5b07db07d642fbcb86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 Nov 2023 17:33:25 +0100 Subject: [PATCH 324/583] Fix formatting --- .../info/StringAnalysisReflectiveCalls.scala | 69 +++---- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 20 +-- .../string_definition/StringTree.scala | 8 +- .../InterproceduralStringAnalysis.scala | 168 +++++++++--------- 4 files changed, 133 insertions(+), 132 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 dfd19133e5..bed2aec769 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 @@ -57,19 +57,19 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] private val relevantCryptoMethodNames = List( - "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" ) private val relevantReflectionMethodNames = 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" + "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", + "java.lang.Class#getField", "java.lang.Class#getDeclaredField", + "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" ) private var includeCrypto = false @@ -80,10 +80,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * by [[buildFQMethodName]]. If the 'crypto' parameter is set, relevant methods of the javax.crypto API are * included, too. */ - private def relevantMethodNames = if(includeCrypto) - relevantReflectionMethodNames ++ relevantCryptoMethodNames + private def relevantMethodNames = if (includeCrypto) + relevantReflectionMethodNames ++ relevantCryptoMethodNames else - relevantReflectionMethodNames + relevantReflectionMethodNames /** * Stores a list of pairs where the first element corresponds to the entities passed to the @@ -139,20 +139,20 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { */ private def instructionsContainRelevantMethod(instructions: Array[Instruction]): Boolean = { - instructions - .filter(_ != null) - .foreach{ instr => - (instr.opcode: @switch) match { - case INVOKESTATIC.opcode => - val INVOKESTATIC(declClass, _, methodName, _) = instr - if (isRelevantCall(declClass, methodName)) return true - case INVOKEVIRTUAL.opcode => - val INVOKEVIRTUAL(declClass, methodName, _) = instr - if (isRelevantCall(declClass, methodName)) return true - } - } + instructions + .filter(_ != null) + .foreach { instr => + (instr.opcode: @switch) match { + case INVOKESTATIC.opcode => + val INVOKESTATIC(declClass, _, methodName, _) = instr + if (isRelevantCall(declClass, methodName)) return true + case INVOKEVIRTUAL.opcode => + val INVOKEVIRTUAL(declClass, methodName, _) = instr + if (isRelevantCall(declClass, methodName)) return true + } + } - false + false } /** @@ -257,11 +257,13 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { override def checkAnalysisSpecificParameters(parameters: Seq[String]): Seq[String] = { - parameters.flatMap{ p => p.toLowerCase match { - case "-includecryptoapi" => Nil - case "-intraprocedural" => Nil - case _ => List(s"Unknown parameter: $p") - }} + parameters.flatMap { p => + p.toLowerCase match { + case "-includecryptoapi" => Nil + case "-intraprocedural" => Nil + case _ => List(s"Unknown parameter: $p") + } + } } @@ -270,16 +272,15 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { ): ReportableAnalysisResult = { // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. - includeCrypto = parameters.exists( p => p.equalsIgnoreCase("-includeCryptoApi")) + includeCrypto = parameters.exists(p => p.equalsIgnoreCase("-includeCryptoApi")) // Check whether intraprocedural analysis should be run. By default, interprocedural is selected. val runIntraproceduralAnalysis = parameters.exists(p => p.equalsIgnoreCase("-intraprocedural")) - val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) implicit val (propertyStore, analyses) = manager.runAll( - if(runIntraproceduralAnalysis) LazyIntraproceduralStringAnalysis else LazyInterproceduralStringAnalysis + if (runIntraproceduralAnalysis) LazyIntraproceduralStringAnalysis else LazyInterproceduralStringAnalysis ) // Stores the obtained results for each supported reflective operation 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 7f2bba7f9f..5ef120cd56 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 @@ -811,20 +811,20 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( }.map(_._2) // If there is one unique such node, we have a 'uniqueExitNode', otherwise not - val uniqueExitNode = if(exitNodes.length == 1) Some(exitNodes.head) else None + val uniqueExitNode = if (exitNodes.length == 1) Some(exitNodes.head) else None // Additional exit nodes are empty if there is only one exit node. Otherwise, they are collected as all nodes // leading into artificial ExitNodes that are NOT the "normalReturnNode", i.e. abnormal termination. // TODO: Verify this is intended behavior for PostDominatorTrees - val additionalExitNodes = if(uniqueExitNode.isDefined) EmptyIntTrieSet else { - IntTrieSet( - basicBlocks - .zipWithIndex - .filter { next => - next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] && !next._1.successors.head.equals(normalReturnNode) - } - .map(_._2) - ) + val additionalExitNodes = if (uniqueExitNode.isDefined) EmptyIntTrieSet else { + IntTrieSet( + basicBlocks + .zipWithIndex + .filter { next => + next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] && !next._1.successors.head.equals(normalReturnNode) + } + .map(_._2) + ) } // All nodes that have an ExitNode successor which is NOT the normalReturnNode are "additionalExitNodes" 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 bc7a6e175d..9be6f79ed1 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 @@ -21,7 +21,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { * is passed). */ private def processReduceCondOrReduceOr( - children: List[StringTree], processOr: Boolean = true + children: List[StringTree], processOr: Boolean = true ): List[StringConstancyInformation] = { val reduced = children.flatMap(reduceAcc) val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) @@ -330,9 +330,9 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { * Otherwise, the number of repetitions is computed by `upperBound - lowerBound`. */ case class StringTreeRepetition( - var child: StringTree, - lowerBound: Option[Int] = None, - upperBound: Option[Int] = None + var child: StringTree, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None ) extends StringTree(ListBuffer(child)) /** 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 d01b0336b9..dc2b22a38c 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 @@ -335,93 +335,93 @@ class InterproceduralStringAnalysis( state.dependees = state.dependees.filter(_.e != eps.e) eps match { - case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => - // 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) - case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => - state.callees = callees - if (state.dependees.isEmpty) { - determinePossibleStrings(state) - } else { - getInterimResult(state) - } - case FinalP(callers: Callers) if eps.pk.equals(Callers.key) => - state.callers = callers - if (state.dependees.isEmpty) { - registerParams(state) - determinePossibleStrings(state) - } else { - getInterimResult(state) - } - case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - 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)) { - val pos = state.paramResultPositions(e) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(e) - state.parameterDependeesCount -= 1 - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(e)) { - state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( - _, p.stringConstancyInformation - )) - // Update the state - 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(e).indexOf(f) - state.entity2Function(e).remove(index) - if (state.entity2Function(e).isEmpty) { - state.entity2Function.remove(e) + case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => + // 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 } - } - // 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) + determinePossibleStrings(state) + case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => + state.callees = callees + if (state.dependees.isEmpty) { + determinePossibleStrings(state) + } else { + getInterimResult(state) + } + case FinalP(callers: Callers) if eps.pk.equals(Callers.key) => + state.callers = callers + if (state.dependees.isEmpty) { + registerParams(state) + determinePossibleStrings(state) + } else { + getInterimResult(state) + } + case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + 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)) { + val pos = state.paramResultPositions(e) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(e) + state.parameterDependeesCount -= 1 } - } - } - if (state.isSetupCompleted && state.parameterDependeesCount == 0) { - processFinalP(state, eps.e, p) - } else { - determinePossibleStrings(state) - } - case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = eps :: state.dependees - val uvar = eps.e.asInstanceOf[P]._1 - state.var2IndexMapping(uvar).foreach { i => - state.appendToInterimFpe2Sci( - i, ub.stringConstancyInformation, Some(uvar) - ) - } - getInterimResult(state) - case _ => - state.dependees = eps :: state.dependees - getInterimResult(state) + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(e)) { + state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( + _, p.stringConstancyInformation + )) + // Update the state + 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(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 + 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(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + state.dependees = eps :: state.dependees + val uvar = eps.e.asInstanceOf[P]._1 + state.var2IndexMapping(uvar).foreach { i => + state.appendToInterimFpe2Sci( + i, ub.stringConstancyInformation, Some(uvar) + ) + } + getInterimResult(state) + case _ => + state.dependees = eps :: state.dependees + getInterimResult(state) } } From f502c8b777296e0c28074a561959fe213bd853da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 23 Nov 2023 16:44:27 +0100 Subject: [PATCH 325/583] Refactor package names --- .../info/StringAnalysisReflectiveCalls.scala | 4 +- .../StringAnalysisMatcher.scala | 7 ++-- .../properties/StringConstancyProperty.scala | 5 ++- .../StringConstancyInformation.scala | 6 ++- .../StringConstancyLevel.scala | 6 ++- .../StringConstancyType.scala | 6 ++- .../string_definition/StringTree.scala | 6 ++- .../org/opalj/br/cfg/DominatorTreeTest.scala | 5 ++- .../StringConstancyLevelTests.scala | 4 +- .../InterproceduralComputationState.scala | 11 +++--- .../InterproceduralStringAnalysis.scala | 19 +++------- .../IntraproceduralStringAnalysis.scala | 12 +++--- .../AbstractStringInterpreter.scala | 21 +++-------- .../InterpretationHandler.scala | 19 +++------- .../common/BinaryExprInterpreter.scala | 14 +++---- .../common/DoubleValueInterpreter.scala | 14 +++---- .../common/FloatValueInterpreter.scala | 14 +++---- .../common/IntegerValueInterpreter.scala | 14 +++---- .../common/NewInterpreter.scala | 14 +++---- .../common/StringConstInterpreter.scala | 14 +++---- .../ArrayPreparationInterpreter.scala | 16 ++++---- .../InterproceduralFieldInterpreter.scala | 16 ++++---- ...InterproceduralInterpretationHandler.scala | 37 +++++-------------- ...ralNonVirtualFunctionCallInterpreter.scala | 15 ++++---- ...duralNonVirtualMethodCallInterpreter.scala | 14 +++---- ...ceduralStaticFunctionCallInterpreter.scala | 16 ++++---- ...oceduralVirtualMethodCallInterpreter.scala | 13 ++++--- .../interprocedural/NewArrayPreparer.scala | 15 ++++---- ...alFunctionCallPreparationInterpreter.scala | 18 ++++----- .../finalizer/AbstractFinalizer.scala | 11 ++++-- .../finalizer/ArrayLoadFinalizer.scala | 15 ++++---- .../finalizer/GetFieldFinalizer.scala | 13 ++++--- .../finalizer/NewArrayFinalizer.scala | 14 ++++--- .../NonVirtualMethodCallFinalizer.scala | 12 ++++-- .../StaticFunctionCallFinalizer.scala | 12 ++++-- .../VirtualFunctionCallFinalizer.scala | 14 ++++--- .../IntraproceduralArrayInterpreter.scala | 15 ++++---- .../IntraproceduralFieldInterpreter.scala | 13 ++++--- .../IntraproceduralGetStaticInterpreter.scala | 13 ++++--- ...IntraproceduralInterpretationHandler.scala | 28 ++++---------- ...ralNonVirtualFunctionCallInterpreter.scala | 13 ++++--- ...duralNonVirtualMethodCallInterpreter.scala | 13 ++++--- ...ceduralStaticFunctionCallInterpreter.scala | 13 ++++--- ...eduralVirtualFunctionCallInterpreter.scala | 14 +++---- ...oceduralVirtualMethodCallInterpreter.scala | 13 ++++--- .../preprocessing/AbstractPathFinder.scala | 14 +++---- .../preprocessing/DefaultPathFinder.scala | 10 +++-- .../string_analysis/preprocessing/Path.scala | 14 +++---- .../preprocessing/PathTransformer.scala | 8 +++- .../preprocessing/WindowPathFinder.scala | 12 +++--- .../string_analysis/string_analysis.scala | 7 ++-- 51 files changed, 325 insertions(+), 341 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 bed2aec769..5e19f29f97 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,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.support.info +package org.opalj +package support +package info import scala.annotation.switch import java.net.URL diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index faf7ef17d1..16e89ae8f7 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -1,11 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis +package org.opalj +package fpcf +package properties +package string_analysis 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 import org.opalj.br.fpcf.properties.StringConstancyProperty /** 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 602d983f1f..d477d4cae8 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 @@ -1,5 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties +package org.opalj +package br +package fpcf +package properties import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason 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 b90ae8e2b7..add21e5e49 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,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties.string_definition +package org.opalj +package br +package fpcf +package properties +package string_definition /** * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala index 289b52c23c..0f18fef894 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties.string_definition +package org.opalj +package br +package fpcf +package properties +package string_definition /** * Values in this enumeration represent the granularity of used strings. diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala index 3227d75e4c..9f508c1933 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties.string_definition +package org.opalj +package br +package fpcf +package properties +package 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/br/fpcf/properties/string_definition/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala index 9be6f79ed1..6026dd5d7b 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 @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.properties.string_definition +package org.opalj +package br +package fpcf +package properties +package string_definition import scala.collection.mutable import scala.collection.mutable.ListBuffer 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 index bf6d266156..04a5a5e191 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala @@ -1,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.cfg +package org.opalj +package br +package cfg import java.net.URL import org.junit.runner.RunWith @@ -9,7 +11,6 @@ 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.scalatestplus.junit.JUnitRunner 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 8b410985a0..bbd1eeca5c 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,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.string_definition +package org.opalj +package br +package string_definition import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.CONSTANT 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 62a36ae116..1be3a4ac40 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 @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis +package org.opalj +package tac +package fpcf +package analyses +package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -13,12 +17,7 @@ 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 -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 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 dc2b22a38c..93401c7bac 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 @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis +package org.opalj +package tac +package fpcf +package analyses +package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -25,29 +29,18 @@ import org.opalj.br.FieldType import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.tac.Stmt +import org.opalj.tac.fpcf.properties.TACAI 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.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.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 import org.opalj.tac.fpcf.analyses.cg.{RTATypeIterator, TypeIterator} /** 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 d36a394a3e..d5b8336779 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 @@ -1,5 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis +package org.opalj +package tac +package fpcf +package analyses +package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -22,8 +26,6 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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 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 @@ -34,10 +36,6 @@ 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.EagerDetachedTACAIKey -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program 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 2ead658640..4f8b82eddf 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,5 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -14,23 +19,9 @@ import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.{NoContext, 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 -import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode 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.cg.TypeIterator 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 /** * @param cfg The control flow graph that underlies the instruction to interpret. 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 43a3d41e18..a7634975b6 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 @@ -1,5 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -13,18 +18,6 @@ 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 -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 -import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { 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 62415cfdca..6ba1c3ef8f 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ 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 -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/common/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala index 67a9e395dc..3e7dab547b 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ 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 -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/common/FloatValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala index 225b1808ea..001a9af8d0 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ 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 -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/common/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala index 66caf979d6..2e97cbd076 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ 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 -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/common/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala index bdf088636e..aacf979631 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,17 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common 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 -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/common/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala index bb01cfecbe..d65f097880 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package common import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ 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 -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/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 8bce835fc7..d508d2a88c 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 @@ -1,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import scala.collection.mutable.ListBuffer @@ -9,14 +15,6 @@ 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.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 -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 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 fc4a44c1a8..7339f68ace 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import scala.collection.mutable.ListBuffer @@ -12,14 +18,6 @@ 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.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 instances of [[FieldRead]]s. 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 0b9a976fb5..c084b6831a 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -14,43 +20,18 @@ 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 -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 -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 +import org.opalj.tac.fpcf.analyses.cg.TypeIterator 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.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 +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.ArrayLoadFinalizer 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 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 -import org.opalj.tac.NewArray -import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NewArrayFinalizer /** 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 65c67a78ff..8fcc1206d4 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -10,14 +16,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.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.ReturnValue import org.opalj.tac.fpcf.analyses.cg.TypeIterator -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 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 692cd9c539..33f0c7d08a 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,12 +15,6 @@ import org.opalj.br.analyses.DeclaredMethods 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 -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 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 3ec874cbcd..186506c70e 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import scala.util.Try import org.opalj.fpcf.Entity @@ -11,14 +17,6 @@ import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.{NoContext, 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 -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.ReturnValue import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** 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 67876bffdb..84a0323504 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -10,11 +16,6 @@ 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 -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/interprocedural/NewArrayPreparer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala index eb232514e4..deda8bf0ef 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 @@ -1,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -7,13 +13,6 @@ 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. 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 841b18e103..ca7a0b6faf 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -16,17 +22,7 @@ import org.opalj.br.fpcf.properties.{NoContext, 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 -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.ReturnValue import org.opalj.tac.fpcf.analyses.cg.TypeIterator -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 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 bb4bcee0bd..9c19bc2199 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,12 @@ /* 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 +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package finalizer /** * When processing instruction interprocedurally, it is not always possible to compute a final diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index d0bdfa7f88..8b2918c824 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -1,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package 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.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 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 0f6ce3a2b1..286b0a277c 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 @@ -1,9 +1,12 @@ /* 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.FieldRead +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package finalizer class GetFieldFinalizer( state: InterproceduralComputationState 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 index 2e413c27e8..02b2cff1e3 100644 --- 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 @@ -1,12 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package 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 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 de96dffe41..c1c83b3c0e 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 @@ -1,11 +1,15 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package 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 /** * @author Patrick Mell 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 index e4cbecbe81..4faa605ee3 100644 --- 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 @@ -1,11 +1,15 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package 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 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 36360ad6d9..b8161f5012 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 @@ -1,16 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package interprocedural +package 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.br.fpcf.properties.StringConstancyProperty -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 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 4ebd6a556c..c51277601b 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 @@ -1,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural import scala.collection.mutable.ListBuffer @@ -9,13 +15,6 @@ 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.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 -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/intraprocedural/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala index 6556cbceb1..585d125b08 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,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural 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 -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/intraprocedural/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala index f79cee0099..90b284cfb1 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,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural 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 `IntraproceduralGetStaticInterpreter` is responsible for processing 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 ae19617d1d..af5121ec51 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -8,32 +14,12 @@ 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 -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 -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 -import org.opalj.tac.DUVar -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode /** * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are 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 13c2ef3f8c..15c8ae799f 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,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural 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 -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/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala index a09c07333c..96cb230940 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 @@ -1,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural import scala.collection.mutable.ListBuffer @@ -9,11 +15,6 @@ 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 -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/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala index 5e565d966a..b8cfd2b6a5 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,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural 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 -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/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala index d25a6c0d22..990511ea50 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -15,12 +21,6 @@ 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 -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/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala index 2862daff13..a18a8ddcee 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,5 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation +package intraprocedural import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -9,11 +15,6 @@ 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 -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/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 583caaef94..b37843c9b4 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 @@ -1,5 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -7,13 +12,6 @@ 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.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 -import org.opalj.tac.fpcf.analyses.string_analysis.V /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index 90d7738048..d2a0b866b5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -1,10 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing 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/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 cf9d2a3d8e..18a4cd4f64 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 @@ -1,17 +1,15 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer 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.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** 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 3ad7d69363..299b5d40ae 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 @@ -1,9 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map - import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index 4247de2a22..c7e89498db 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/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.tac.fpcf.analyses.string_analysis.preprocessing +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing 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/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 bd7c787454..b3cc24c894 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,5 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses +package org.opalj +package tac +package fpcf +package analyses import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -9,8 +12,6 @@ import org.opalj.fpcf.EOptionP 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 /** * @author Patrick Mell From 7098d2aef32e5d920d5fa3c5b005b69c9ab14f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 28 Nov 2023 21:10:06 +0100 Subject: [PATCH 326/583] Fix some typos in test --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 e40fef479f..43ed292674 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 @@ -143,14 +143,14 @@ object StringAnalysisTestRunner { } /** - * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some - * well-defined tests. + * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis]] works correctly with + * respect to some well-defined tests. * * @author Patrick Mell */ class IntraproceduralStringAnalysisTest extends PropertiesTest { - describe("the org.opalj.fpcf.LocalStringAnalysis is started") { + describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { val runner = new StringAnalysisTestRunner( IntraproceduralStringAnalysisTest.fqTestMethodsClass, IntraproceduralStringAnalysisTest.nameTestMethod, @@ -192,8 +192,8 @@ object IntraproceduralStringAnalysisTest { } /** - * Tests whether the InterproceduralStringAnalysis works correctly with respect to some - * well-defined tests. + * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis]] works correctly with + * respect to some well-defined tests. * * @author Patrick Mell */ From 9f563c94ce6d99607568286f2f237875888a734d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 29 Nov 2023 11:01:00 +0100 Subject: [PATCH 327/583] Fix concurrent modification in string tree --- .../properties/string_definition/StringTree.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 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 6026dd5d7b..3c30c04967 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 @@ -15,7 +15,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * * @author Patrick Mell */ -sealed abstract class StringTree(val children: ListBuffer[StringTree]) { +sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // question do it even make sense to have mutable here? /** * This is a helper function which processes the `reduce` operation for [[StringTreeOr]] and @@ -190,25 +190,26 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { private def simplifyAcc(subtree: StringTree): StringTree = { subtree match { case StringTreeOr(cs) => + val newChildren = cs.clone() cs.foreach { case nextC @ StringTreeOr(subChildren) => simplifyAcc(nextC) - var insertIndex = subtree.children.indexOf(nextC) + var insertIndex = newChildren.indexOf(nextC) subChildren.foreach { next => - subtree.children.insert(insertIndex, next) + newChildren.insert(insertIndex, next) insertIndex += 1 } - subtree.children.-=(nextC) + newChildren.-=(nextC) case _ => } - val unique = removeDuplicateTreeValues(cs) + val unique = removeDuplicateTreeValues(newChildren) subtree.children.clear() subtree.children.appendAll(unique) 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)*). + // expressions like ((s)*)+ will follow which is equivalent to (s)*). question isn't this q mark, see p.24? if (stc.children.nonEmpty && stc.children.head.isInstanceOf[StringTreeRepetition]) { stc.children.head } else { @@ -316,7 +317,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { * * @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 + * @note Applying this function changes the representation of the tree but does not produce a * semantically different tree! */ def groupRepetitionElements(): StringTree = groupRepetitionElementsAcc(this) From 84f25945f8c113d07a2efd0348a2386bf55307e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 21 Dec 2023 17:27:54 +0100 Subject: [PATCH 328/583] Fix test file formatting --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 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 43ed292674..967eb8975e 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 @@ -45,7 +45,7 @@ sealed class StringAnalysisTestRunner( "properties/string_analysis/StringDefinitions.class" ) ++ filesToLoad val basePath = System.getProperty("user.dir")+ - "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/" + "/DEVELOPING_OPAL/validate/target/scala-2.13/test-classes/org/opalj/fpcf/" // TODO anti-hardcode necessaryFiles.map { filePath => new File(basePath + filePath) } } @@ -185,10 +185,9 @@ object IntraproceduralStringAnalysisTest { // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" // Files to load for the runner - val filesToLoad = List( + val filesToLoad: List[String] = List( "fixtures/string_analysis/LocalTestMethods.class" ) - } /** @@ -240,12 +239,11 @@ object InterproceduralStringAnalysisTest { // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" // Files to load for the runner - val filesToLoad = List( + val filesToLoad: List[String] = List( "fixtures/string_analysis/InterproceduralTestMethods.class", "fixtures/string_analysis/StringProvider.class", "fixtures/string_analysis/hierarchies/GreetingService.class", "fixtures/string_analysis/hierarchies/HelloGreeting.class", "fixtures/string_analysis/hierarchies/SimpleHelloGreeting.class" ) - } From feb515593df70538fee0e27876f0788eb7016438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 21 Dec 2023 18:02:14 +0100 Subject: [PATCH 329/583] Fix compilation problems --- .../support/info/ClassUsageAnalysis.scala | 2 +- .../InterproceduralStringAnalysis.scala | 6 ++- .../InterproceduralFieldInterpreter.scala | 48 +++++++++---------- ...InterproceduralInterpretationHandler.scala | 8 ++-- 4 files changed, 33 insertions(+), 31 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 2c952f3b4e..8c5738b08d 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 @@ -16,7 +16,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.fpcf.analyses.cg.V +import org.opalj.tac.V import org.opalj.tac.EagerDetachedTACAIKey /** 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 93401c7bac..4e8fc78278 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.FPCFLazyAnalysisScheduler 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.DeclaredFieldsKey import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -94,6 +95,7 @@ class InterproceduralStringAnalysis( */ private val fieldWriteThreshold = 100 private val declaredMethods = project.get(DeclaredMethodsKey) + private val declaredFields = project.get(DeclaredFieldsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) /** @@ -175,7 +177,7 @@ class InterproceduralStringAnalysis( if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, state, typeIterator + state.tac, ps, declaredMethods, declaredFields, fieldAccessInformation, state, typeIterator ) val interimState = state.copy() interimState.tac = state.tac @@ -184,7 +186,7 @@ class InterproceduralStringAnalysis( interimState.callers = state.callers interimState.params = state.params state.interimIHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, interimState, typeIterator + state.tac, ps, declaredMethods, declaredFields, fieldAccessInformation, interimState, typeIterator ) } 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 7339f68ace..514700d191 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,17 +7,20 @@ package string_analysis package interpretation package interprocedural -import scala.collection.mutable.ListBuffer +import org.opalj.br.analyses.DeclaredFields +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.PropertyStore import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.analyses.cg.uVarForDefSites /** * The `InterproceduralFieldInterpreter` is responsible for processing instances of [[FieldRead]]s. @@ -29,10 +32,12 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel * @author Patrick Mell */ class InterproceduralFieldInterpreter( - state: InterproceduralComputationState, - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation + state: InterproceduralComputationState, + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + implicit val declaredFields: DeclaredFields, + implicit val contextProvider: ContextProvider ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { override type T = FieldRead[V] @@ -59,7 +64,8 @@ class InterproceduralFieldInterpreter( return FinalEP(instr, StringConstancyProperty.lb) } // Write accesses exceeds the threshold => approximate with lower bound - val writeAccesses = fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name) + val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField + val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField) if (writeAccesses.length > state.fieldWriteThreshold) { return FinalEP(instr, StringConstancyProperty.lb) } @@ -67,18 +73,22 @@ class InterproceduralFieldInterpreter( var hasInit = false val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() writeAccesses.foreach { - case (m, pcs) => pcs.foreach { pc => - if (m.name == "" || m.name == "") { + case (contextId, _, _, parameter) => + val method = contextProvider.contextFromId(contextId).method.definedMethod + + if (method.name == "" || method.name == "") { hasInit = true } - val (tacEps, tac) = getTACAI(ps, m, state) - val nextResult = if (tacEps.isRefinable) { + val (tacEps, tac) = getTACAI(ps, method, state) + val nextResult = if (parameter.isEmpty) { + // Field parameter information is not available + FinalEP(defSitEntity, StringConstancyProperty.lb) + } else if (tacEps.isRefinable) { EPK(state.entity, StringConstancyProperty.key) } else { tac match { case Some(methodTac) => - val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) - val entity = (extractUVarFromPut(stmt), m) + val entity = (uVarForDefSites(parameter.get, methodTac.pcToIndex), method) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { state.dependees = eps :: state.dependees @@ -96,7 +106,6 @@ class InterproceduralFieldInterpreter( } } results.append(nextResult) - } } if (results.isEmpty) { @@ -123,7 +132,7 @@ class InterproceduralFieldInterpreter( )) } val finalSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + _.asFinal.p.stringConstancyInformation }) state.appendToFpe2Sci(defSitEntity, finalSci) FinalEP(defSitEntity, StringConstancyProperty(finalSci)) @@ -132,15 +141,4 @@ 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/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index c084b6831a..f14e0f0c83 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 @@ -20,6 +20,7 @@ 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.br.analyses.DeclaredFields import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter @@ -49,6 +50,7 @@ class InterproceduralInterpretationHandler( tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, declaredMethods: DeclaredMethods, + declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, typeIterator: TypeIterator @@ -268,7 +270,7 @@ class InterproceduralInterpretationHandler( expr: FieldRead[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( - state, this, ps, fieldAccessInformation + state, this, ps, fieldAccessInformation, declaredFields, typeIterator ).interpret(expr, defSite) if (r.isRefinable) { processedDefSites.remove(defSite) @@ -404,11 +406,11 @@ object InterproceduralInterpretationHandler { tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, declaredMethods: DeclaredMethods, + declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, typeIterator: TypeIterator ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - tac, ps, declaredMethods, fieldAccessInformation, state, typeIterator + tac, ps, declaredMethods, declaredFields, fieldAccessInformation, state, typeIterator ) - } \ No newline at end of file From a73f901754b45d0e81e0efd0248294fd0f6a7b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 25 Jan 2024 12:12:43 +0100 Subject: [PATCH 330/583] Apply new formatting from scalafmt --- .../support/info/ClassUsageAnalysis.scala | 24 +-- .../info/StringAnalysisReflectiveCalls.scala | 114 ++++++++------ .../org/opalj/fpcf/StringAnalysisTest.scala | 25 ++- .../StringAnalysisMatcher.scala | 10 +- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 20 ++- .../properties/StringConstancyProperty.scala | 9 +- .../StringConstancyInformation.scala | 10 +- .../StringConstancyLevel.scala | 9 +- .../string_definition/StringTree.scala | 28 ++-- .../org/opalj/br/cfg/AbstractCFGTest.scala | 8 +- .../org/opalj/br/cfg/DominatorTreeTest.scala | 16 +- .../StringConstancyLevelTests.scala | 12 +- .../org/opalj/graphs/DominatorTree.scala | 6 +- .../org/opalj/graphs/PostDominatorTree.scala | 4 +- .../InterproceduralComputationState.scala | 24 +-- .../InterproceduralStringAnalysis.scala | 140 +++++++++++------ .../IntraproceduralStringAnalysis.scala | 55 ++++--- .../AbstractStringInterpreter.scala | 24 +-- .../InterpretationHandler.scala | 15 +- .../common/BinaryExprInterpreter.scala | 10 +- .../common/DoubleValueInterpreter.scala | 21 +-- .../common/FloatValueInterpreter.scala | 25 +-- .../common/IntegerValueInterpreter.scala | 21 +-- .../common/NewInterpreter.scala | 4 +- .../common/StringConstInterpreter.scala | 21 +-- .../ArrayPreparationInterpreter.scala | 16 +- .../InterproceduralFieldInterpreter.scala | 25 +-- ...InterproceduralInterpretationHandler.scala | 145 +++++++++++++----- ...ralNonVirtualFunctionCallInterpreter.scala | 6 +- ...duralNonVirtualMethodCallInterpreter.scala | 18 ++- ...ceduralStaticFunctionCallInterpreter.scala | 25 +-- ...oceduralVirtualMethodCallInterpreter.scala | 15 +- .../interprocedural/NewArrayPreparer.scala | 7 +- ...alFunctionCallPreparationInterpreter.scala | 48 +++--- .../finalizer/ArrayLoadFinalizer.scala | 13 +- .../finalizer/NewArrayFinalizer.scala | 6 +- .../NonVirtualMethodCallFinalizer.scala | 4 +- .../StaticFunctionCallFinalizer.scala | 4 +- .../VirtualFunctionCallFinalizer.scala | 17 +- .../IntraproceduralArrayInterpreter.scala | 17 +- .../IntraproceduralFieldInterpreter.scala | 4 +- .../IntraproceduralGetStaticInterpreter.scala | 6 +- ...IntraproceduralInterpretationHandler.scala | 21 ++- ...ralNonVirtualFunctionCallInterpreter.scala | 4 +- ...duralNonVirtualMethodCallInterpreter.scala | 9 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...eduralVirtualFunctionCallInterpreter.scala | 23 +-- ...oceduralVirtualMethodCallInterpreter.scala | 13 +- .../preprocessing/AbstractPathFinder.scala | 129 ++++++++++------ .../string_analysis/preprocessing/Path.scala | 36 +++-- .../preprocessing/PathTransformer.scala | 20 +-- .../string_analysis/string_analysis.scala | 4 +- 52 files changed, 784 insertions(+), 510 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 e31d073ec0..335d8caea2 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 @@ -4,11 +4,11 @@ package support package 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.br.analyses.BasicReport import org.opalj.br.analyses.Project import org.opalj.br.analyses.ProjectAnalysisApplication @@ -16,10 +16,10 @@ import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.log.GlobalLogContext import org.opalj.tac.Assignment import org.opalj.tac.Call +import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.ExprStmt -import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.V -import org.opalj.tac.EagerDetachedTACAIKey +import org.opalj.tac.VirtualFunctionCall /** * Analyzes a project for how a particular class is used within that project. Collects information @@ -39,7 +39,7 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { override def title: String = "Class Usage Analysis" override def description: String = { - "Analysis a project for how a particular class is used, i.e., which methods are called "+ + "Analysis a project for how a particular class is used, i.e., which methods are called " + "on it" } @@ -161,15 +161,15 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { tacProvider(m).stmts.foreach { stmt => (stmt.astID: @switch) match { case Assignment.ASTID => stmt match { - case Assignment(_, _, c: VirtualFunctionCall[V]) => - processFunctionCall(c, resultMap) - case _ => - } + case Assignment(_, _, c: VirtualFunctionCall[V]) => + processFunctionCall(c, resultMap) + case _ => + } case ExprStmt.ASTID => stmt match { - case ExprStmt(_, c: VirtualFunctionCall[V]) => - processFunctionCall(c, resultMap) - case _ => - } + case ExprStmt(_, c: VirtualFunctionCall[V]) => + processFunctionCall(c, resultMap) + case _ => + } case _ => } } 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 5e19f29f97..5d7675b30d 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 @@ -4,9 +4,24 @@ package support package info import scala.annotation.switch + import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer + +import org.opalj.br.Method +import org.opalj.br.ReferenceType +import org.opalj.br.analyses.BasicReport +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.ProjectAnalysisApplication +import org.opalj.br.analyses.ReportableAnalysisResult +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +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.instructions.Instruction +import org.opalj.br.instructions.INVOKESTATIC +import org.opalj.br.instructions.INVOKEVIRTUAL import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimELUBP import org.opalj.fpcf.InterimLUBP @@ -15,28 +30,18 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.br.analyses.BasicReport -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.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.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.StaticFunctionCall -import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.fpcf.analyses.string_analysis.{LazyInterproceduralStringAnalysis, LazyIntraproceduralStringAnalysis, P, V} -import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.Stmt +import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis +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.properties.TACAI /** * Analyzes a project for calls provided by the Java Reflection API and tries to determine which @@ -59,19 +64,29 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] private val relevantCryptoMethodNames = List( - "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" ) private val relevantReflectionMethodNames = 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" + "java.lang.Class#forName", + "java.lang.ClassLoader#loadClass", + "java.lang.Class#getField", + "java.lang.Class#getDeclaredField", + "java.lang.Class#getMethod", + "java.lang.Class#getDeclaredMethod" ) private var includeCrypto = false @@ -111,7 +126,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { 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 "+ + "Finds calls to methods provided by the Java Reflection API and tries to resolve passed " + "string values" } @@ -163,7 +178,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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 + ps: PropertyStore, + method: Method, + call: Call[V], + resultMap: ResultMapType ): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) @@ -224,26 +242,28 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { // 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 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 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 + ps: PropertyStore, + m: Method, + resultMap: ResultMapType )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) => @@ -251,7 +271,11 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { Result(m, tac) case InterimLUBP(lb, ub) => InterimResult( - m, lb, ub, Set(eps), continuation(ps, m, resultMap) + m, + lb, + ub, + Set(eps), + continuation(ps, m, resultMap) ) case _ => throw new IllegalStateException("should never happen!") } @@ -270,7 +294,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } override def doAnalyze( - project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean + project: Project[URL], + parameters: Seq[String], + isInterrupted: () => Boolean ): ReportableAnalysisResult = { // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. @@ -323,7 +349,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { case InterimELUBP(_, _, ub: StringConstancyProperty) => resultMap(callName).append(ub.stringConstancyInformation) case _ => - println(s"Neither a final nor an interim result for $e in $callName; "+ + println(s"Neither a final nor an interim result for $e in $callName; " + "this should never be the case!") } } 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 967eb8975e..ec3dff1885 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 @@ -4,25 +4,24 @@ package fpcf import java.io.File import java.net.URL - import scala.collection.mutable.ListBuffer -import org.opalj.br.analyses.Project import org.opalj.br.Annotation +import org.opalj.br.Annotations import org.opalj.br.Method +import org.opalj.br.analyses.Project 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.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis 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 /** @@ -44,7 +43,7 @@ sealed class StringAnalysisTestRunner( val necessaryFiles = Array( "properties/string_analysis/StringDefinitions.class" ) ++ filesToLoad - val basePath = System.getProperty("user.dir")+ + val basePath = System.getProperty("user.dir") + "/DEVELOPING_OPAL/validate/target/scala-2.13/test-classes/org/opalj/fpcf/" // TODO anti-hardcode necessaryFiles.map { filePath => new File(basePath + filePath) } @@ -69,15 +68,15 @@ sealed class StringAnalysisTestRunner( val entitiesToAnalyze = ListBuffer[(V, Method)]() val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.filter { - _.runtimeInvisibleAnnotations.foldLeft(false)( - (exists, a) => exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) + _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => + exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) ) } foreach { m => StringAnalysisTestRunner.extractUVars( - tacProvider(m).cfg, fqTestMethodsClass, nameTestMethod - ).foreach { uvar => - entitiesToAnalyze.append((uvar, m)) - } + tacProvider(m).cfg, + fqTestMethodsClass, + nameTestMethod + ).foreach { uvar => entitiesToAnalyze.append((uvar, m)) } } entitiesToAnalyze } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index 16e89ae8f7..e8018a8fb4 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -4,9 +4,9 @@ package fpcf package properties package string_analysis -import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike import org.opalj.br.ObjectType +import org.opalj.br.analyses.Project import org.opalj.br.fpcf.properties.StringConstancyProperty /** @@ -29,8 +29,8 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { 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" - ) + "Can only extract the constancy level from a StringDefinitions annotation" + ) } } @@ -46,8 +46,8 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { a.elementValuePairs.find(_.name == "expectedStrings") match { case Some(el) => el.value.asStringValue.value case None => throw new IllegalArgumentException( - "Can only extract the possible strings from a StringDefinitions annotation" - ) + "Can only extract the possible strings from a StringDefinitions annotation" + ) } } 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 be4b0a5f36..1b41202b70 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 @@ -6,12 +6,12 @@ package cfg import scala.reflect.ClassTag import java.util.Arrays - -import org.opalj.collection.immutable.EmptyIntTrieSet - import scala.collection.{Set => SomeSet} import scala.collection.AbstractIterator +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.collection.immutable.IntTrieSet1 import org.opalj.collection.mutable.FixedSizedHashIDMap @@ -24,9 +24,6 @@ import org.opalj.log.GlobalLogContext import org.opalj.log.LogContext import org.opalj.log.OPALLogger.info -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * Represents the control flow graph of a method. * @@ -812,12 +809,15 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( // Additional exit nodes are empty if there is only one exit node. Otherwise, they are collected as all nodes // leading into artificial ExitNodes that are NOT the "normalReturnNode", i.e. abnormal termination. // TODO: Verify this is intended behavior for PostDominatorTrees - val additionalExitNodes = if (uniqueExitNode.isDefined) EmptyIntTrieSet else { + val additionalExitNodes = if (uniqueExitNode.isDefined) EmptyIntTrieSet + else { IntTrieSet( basicBlocks .zipWithIndex .filter { next => - next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] && !next._1.successors.head.equals(normalReturnNode) + next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ + ExitNode + ] && !next._1.successors.head.equals(normalReturnNode) } .map(_._2) ) @@ -833,9 +833,7 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( (f: Int => Unit) => exitNodes.foreach(e => f(e)), foreachSuccessor, foreachPredecessor, - basicBlocks.foldLeft(0) { (prevMaxNode: Int, next: BasicBlock) => - math.max(prevMaxNode, next.endPC) - } + basicBlocks.foldLeft(0) { (prevMaxNode: Int, next: BasicBlock) => math.max(prevMaxNode, next.endPC) } ) } 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 d477d4cae8..d2e8a36b7b 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 @@ -4,15 +4,15 @@ package br package fpcf package properties +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.fpcf.Entity 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.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 { final type Self = StringConstancyProperty @@ -76,7 +76,8 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta */ def ub: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.APPEND + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND )) /** 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 add21e5e49..6c732ec542 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 @@ -85,14 +85,18 @@ object StringConstancyInformation { val reduced = relScis.reduceLeft((o, n) => StringConstancyInformation( StringConstancyLevel.determineMoreGeneral( - o.constancyLevel, n.constancyLevel + 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})" + reduced.constancyLevel, + reduced.constancyType, + s"(${reduced.possibleStrings})" ) } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala index 0f18fef894..39d22d1361 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala @@ -42,7 +42,8 @@ object StringConstancyLevel extends Enumeration { * @return Returns the more general level of both given inputs. */ def determineMoreGeneral( - level1: StringConstancyLevel, level2: StringConstancyLevel + level1: StringConstancyLevel, + level2: StringConstancyLevel ): StringConstancyLevel = { if (level1 == DYNAMIC || level2 == DYNAMIC) { DYNAMIC @@ -65,12 +66,14 @@ object StringConstancyLevel extends Enumeration { * @return Returns the level for a concatenation. */ def determineForConcat( - level1: StringConstancyLevel, level2: StringConstancyLevel + level1: StringConstancyLevel, + level2: StringConstancyLevel ): StringConstancyLevel = { if (level1 == PARTIALLY_CONSTANT || level2 == PARTIALLY_CONSTANT) { PARTIALLY_CONSTANT } else if ((level1 == CONSTANT && level2 == DYNAMIC) || - (level1 == DYNAMIC && level2 == CONSTANT)) { + (level1 == DYNAMIC && level2 == CONSTANT) + ) { PARTIALLY_CONSTANT } else { level1 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 3c30c04967..dbf9637948 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 @@ -25,18 +25,21 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * is passed). */ private def processReduceCondOrReduceOr( - children: List[StringTree], processOr: Boolean = true + children: List[StringTree], + processOr: Boolean = true ): List[StringConstancyInformation] = { val reduced = children.flatMap(reduceAcc) val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) val replaceElement = reduced.find(_.constancyType == StringConstancyType.REPLACE) val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } val appendSci = if (appendElements.nonEmpty) { - Some(appendElements.reduceLeft((o, n) => StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"${o.possibleStrings}|${n.possibleStrings}" - ))) + Some(appendElements.reduceLeft((o, n) => + StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + ) + )) } else { None } @@ -53,7 +56,9 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques possibleStrings = s"(${appendSci.get.possibleStrings})?" } scis.append(StringConstancyInformation( - appendSci.get.constancyLevel, appendSci.get.constancyType, possibleStrings + appendSci.get.constancyLevel, + appendSci.get.constancyType, + possibleStrings )) } if (resetElement.isDefined) { @@ -112,7 +117,8 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques case (innerSci, index) => val collapsed = StringConstancyInformation( StringConstancyLevel.determineForConcat( - innerSci.constancyLevel, nextSci.constancyLevel + innerSci.constancyLevel, + nextSci.constancyLevel ), StringConstancyType.APPEND, innerSci.possibleStrings + nextSci.possibleStrings @@ -142,9 +148,11 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques subtree match { case StringTreeRepetition(c, lowerBound, upperBound) => val times = if (lowerBound.isDefined && upperBound.isDefined) - (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol + (upperBound.get - lowerBound.get).toString + else InfiniteRepetitionSymbol val reducedAcc = reduceAcc(c) - val reduced = if (reducedAcc.nonEmpty) reducedAcc.head else + val reduced = if (reducedAcc.nonEmpty) reducedAcc.head + else StringConstancyInformation.lb List(StringConstancyInformation( reduced.constancyLevel, 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 2a58ddfe23..9713a099a6 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,10 +5,10 @@ package cfg import org.scalatest.BeforeAndAfterAll import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -import org.scalatest.BeforeAndAfterAll -import org.opalj.io.writeAndOpen + import org.opalj.br.instructions.Instruction import org.opalj.graphs.DominatorTree +import org.opalj.io.writeAndOpen /** * Helper methods to test the CFG related methods. @@ -95,7 +95,7 @@ abstract class AbstractCFGTest extends AnyFunSpec with Matchers with BeforeAndAf method: Method, code: Code, cfg: CFG[Instruction, Code], - domTree: Option[DominatorTree] = None + domTree: Option[DominatorTree] = None )( f: => Unit )( @@ -108,7 +108,7 @@ abstract class AbstractCFGTest extends AnyFunSpec with Matchers with BeforeAndAf case t: Throwable => writeAndOpen(cfg.toDot, method.name + "-CFG", ".gv") if (domTree.isDefined) { - writeAndOpen(domTree.get.toDot(), method.name+"-DomTree", ".gv") + 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 index 04a5a5e191..f259ed5d9a 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala @@ -4,16 +4,18 @@ package br package cfg import java.net.URL + import org.junit.runner.RunWith -import org.opalj.br.analyses.Project +import org.scalatestplus.junit.JUnitRunner + import org.opalj.br.ClassHierarchy import org.opalj.br.ObjectType import org.opalj.br.TestSupport.biProject +import org.opalj.br.analyses.Project import org.opalj.br.instructions.IF_ICMPNE -import org.opalj.br.instructions.IFNE import org.opalj.br.instructions.IFEQ +import org.opalj.br.instructions.IFNE import org.opalj.br.instructions.ILOAD -import org.scalatestplus.junit.JUnitRunner /** * Computes the dominator tree of CFGs of a couple of methods and checks their sanity. @@ -47,7 +49,7 @@ class DominatorTreeTest extends AbstractCFGTest { implicit val testClassHierarchy: ClassHierarchy = testProject.classHierarchy - it("the dominator tree of a CFG with no control flow should be a tree where each "+ + 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 @@ -66,8 +68,8 @@ class DominatorTreeTest extends AbstractCFGTest { } } - 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 "+ + 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 @@ -88,7 +90,7 @@ class DominatorTreeTest extends AbstractCFGTest { } } - it("in a dominator tree of a CFG with an if-else right before the return, the return "+ + 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 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 bbd1eeca5c..1b4db4bd14 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 @@ -3,11 +3,12 @@ package org.opalj package br package string_definition +import org.scalatest.funsuite.AnyFunSuite + 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 -import org.scalatest.funsuite.AnyFunSuite /** * Tests for [[StringConstancyLevel]] methods. @@ -21,7 +22,8 @@ class StringConstancyLevelTests extends AnyFunSuite { // Trivial cases assert(StringConstancyLevel.determineMoreGeneral(DYNAMIC, DYNAMIC) == DYNAMIC) assert(StringConstancyLevel.determineMoreGeneral( - PARTIALLY_CONSTANT, PARTIALLY_CONSTANT + PARTIALLY_CONSTANT, + PARTIALLY_CONSTANT ) == PARTIALLY_CONSTANT) assert(StringConstancyLevel.determineMoreGeneral(CONSTANT, CONSTANT) == CONSTANT) @@ -35,10 +37,12 @@ class StringConstancyLevelTests extends AnyFunSuite { // { PARTIALLY_CONSTANT, CONSTANT } assert(StringConstancyLevel.determineMoreGeneral( - PARTIALLY_CONSTANT, CONSTANT + PARTIALLY_CONSTANT, + CONSTANT ) == PARTIALLY_CONSTANT) assert(StringConstancyLevel.determineMoreGeneral( - CONSTANT, PARTIALLY_CONSTANT + CONSTANT, + PARTIALLY_CONSTANT ) == PARTIALLY_CONSTANT) } 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 a12034482e..3e17e9829a 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala @@ -36,7 +36,8 @@ final class DominatorTree private ( * identified by `possibleDominator`. Otherwise, false will be returned. */ def doesDominate( - possibleDominator: Int, toCheck: Int + possibleDominator: Int, + toCheck: Int ): Boolean = doesDominate(Array(possibleDominator), toCheck) /** @@ -50,7 +51,8 @@ final class DominatorTree private ( * number of possible dominators. */ def doesDominate( - possibleDominators: Array[Int], toCheck: Int + possibleDominators: Array[Int], + toCheck: Int ): Boolean = { var nextToCheck = toCheck var pd = possibleDominators.filter(_ < nextToCheck) 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 f298cd3029..918ed5af92 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala @@ -2,10 +2,10 @@ package org.opalj package graphs -import org.opalj.collection.immutable.IntTrieSet - import scala.collection.mutable.ListBuffer +import org.opalj.collection.immutable.IntTrieSet + /** * A representation of a post-dominator tree (see [[PostDominatorTree$#apply*]] * for details regarding the properties). 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 1be3a4ac40..fef66a91f1 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,17 +7,18 @@ package 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.value.ValueInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation + import org.opalj.br.DeclaredMethod 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.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.value.ValueInformation /** * This class is to be used to store state information that are required at a later point in @@ -157,7 +158,9 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW * the list of `defSite`. */ def appendToFpe2Sci( - defSite: Int, sci: StringConstancyInformation, reset: Boolean = false + defSite: Int, + sci: StringConstancyInformation, + reset: Boolean = false ): Unit = { if (reset || !fpe2sci.contains(defSite)) { fpe2sci(defSite) = ListBuffer() @@ -193,7 +196,9 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW * @param entity Optional. The entity for which the `sci` element was computed. */ def appendToInterimFpe2Sci( - defSite: Int, sci: StringConstancyInformation, entity: Option[V] = None + defSite: Int, + sci: StringConstancyInformation, + entity: Option[V] = None ): Unit = { val numElements = var2IndexMapping.values.flatten.count(_ == defSite) var addedNewList = false @@ -210,7 +215,8 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW interimFpe2sci(defSite).append(sci) } else if (!containsSci && entity.nonEmpty) { if (!entity2lastInterimFpe2SciValue.contains(entity.get) || - entity2lastInterimFpe2SciValue(entity.get) == StringConstancyInformation.lb) { + entity2lastInterimFpe2SciValue(entity.get) == StringConstancyInformation.lb + ) { entity2lastInterimFpe2SciValue(entity.get) = sci if (interimFpe2sci(defSite).nonEmpty) { interimFpe2sci(defSite).remove(0) 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 4e8fc78278..be735c0af4 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 @@ -7,6 +7,21 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer + +import org.opalj.br.FieldType +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.FieldAccessInformationKey +import org.opalj.br.analyses.ProjectInformationKeys +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.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -18,31 +33,21 @@ 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.{DeclaredMethodsKey, FieldAccessInformationKey, ProjectInformationKeys, SomeProject} -import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.FPCFAnalysisScheduler -import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -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.DeclaredFieldsKey -import org.opalj.br.fpcf.properties.cg.Callers -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.analyses.cg.RTATypeIterator +import org.opalj.tac.fpcf.analyses.cg.TypeIterator +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler 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.NestedPathType 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.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.SubPath -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter -import org.opalj.tac.fpcf.analyses.cg.{RTATypeIterator, TypeIterator} +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.value.ValueInformation /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -73,7 +78,7 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { - //TODO: How do we pass the type iterator along? How do we decide which to use? + // TODO: How do we pass the type iterator along? How do we decide which to use? private val typeIterator: TypeIterator = new RTATypeIterator(project) // TODO: Is it possible to make the following two parameters configurable from the outside? @@ -117,7 +122,8 @@ class InterproceduralStringAnalysis( ): StringConstancyProperty = { if (state.computedLeanPath != null) { StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( - state.computedLeanPath, state.interimFpe2sci + state.computedLeanPath, + state.interimFpe2sci ).reduce(true)) } else { StringConstancyProperty.lb @@ -177,7 +183,13 @@ class InterproceduralStringAnalysis( if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, declaredFields, fieldAccessInformation, state, typeIterator + state.tac, + ps, + declaredMethods, + declaredFields, + fieldAccessInformation, + state, + typeIterator ) val interimState = state.copy() interimState.tac = state.tac @@ -186,7 +198,13 @@ class InterproceduralStringAnalysis( interimState.callers = state.callers interimState.params = state.params state.interimIHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, declaredFields, fieldAccessInformation, interimState, typeIterator + state.tac, + ps, + declaredMethods, + declaredFields, + fieldAccessInformation, + interimState, + typeIterator ) } @@ -203,8 +221,7 @@ class InterproceduralStringAnalysis( hasCallersOrParamInfo } else if (InterproceduralStringAnalysis.isSupportedPrimitiveNumberType(uvar)) { val numType = uvar.value.asPrimitiveValue.primitiveType.toJava - val sci = InterproceduralStringAnalysis. - getDynamicStringInformationForNumberType(numType) + val sci = InterproceduralStringAnalysis.getDynamicStringInformationForNumberType(numType) return Result(state.entity, StringConstancyProperty(sci)) } else { // StringBuilders as parameters are currently not evaluated @@ -301,7 +318,8 @@ class InterproceduralStringAnalysis( StringConstancyInformation.getNeutralElement } else { new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci + state.computedLeanPath, + state.fpe2sci ).reduce(true) } } @@ -370,7 +388,8 @@ class InterproceduralStringAnalysis( // If necessary, update parameter information of function calls if (state.entity2Function.contains(e)) { state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( - _, p.stringConstancyInformation + _, + p.stringConstancyInformation )) // Update the state state.entity2Function(e).foreach { f => @@ -405,12 +424,15 @@ class InterproceduralStringAnalysis( } else { determinePossibleStrings(state) } - case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) + if eps.pk.equals(StringConstancyProperty.key) => state.dependees = eps :: state.dependees val uvar = eps.e.asInstanceOf[P]._1 state.var2IndexMapping(uvar).foreach { i => state.appendToInterimFpe2Sci( - i, ub.stringConstancyInformation, Some(uvar) + i, + ub.stringConstancyInformation, + Some(uvar) ) } getInterimResult(state) @@ -450,7 +472,9 @@ class InterproceduralStringAnalysis( private def computeFinalResult(state: InterproceduralComputationState): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci, resetExprHandler = false + state.computedLeanPath, + state.fpe2sci, + resetExprHandler = false ).reduce(true) InterproceduralStringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(finalSci)) @@ -520,7 +544,8 @@ class InterproceduralStringAnalysis( // parameters there are) if (state.params.length <= methodIndex) { state.params.append(ListBuffer.from(params.indices.map(_ => - StringConstancyInformation.getNeutralElement))) + StringConstancyInformation.getNeutralElement + ))) } // Recursively analyze supported types if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { @@ -578,7 +603,8 @@ class InterproceduralStringAnalysis( } case npe: NestedPathElement => val subFinalResult = computeResultsForPath( - Path(npe.element.toList), state + Path(npe.element.toList), + state ) if (hasFinalResult) { hasFinalResult = subFinalResult @@ -594,7 +620,8 @@ class InterproceduralStringAnalysis( * [[computeLeanPathForStringBuilder]]. */ private def computeLeanPath( - duvar: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + duvar: V, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): Path = { val defSites = duvar.definedBy.toArray.sorted if (defSites.head < 0) { @@ -623,9 +650,7 @@ class InterproceduralStringAnalysis( // 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)) - } + defSites.foreach { ds => children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) } Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) } } @@ -640,7 +665,8 @@ class InterproceduralStringAnalysis( * `(null, false)` and otherwise `(computed lean path, true)`. */ private def computeLeanPathForStringBuilder( - duvar: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + duvar: V, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): (Path, Boolean) = { val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) val initDefSites = InterpretationHandler.findDefSiteOfInit(duvar, tac.stmts) @@ -665,10 +691,10 @@ class InterproceduralStringAnalysis( path.elements.exists { case FlatPathElement(index) => stmts(index) match { - case Assignment(_, _, expr) => hasExprParamUsage(expr) - case ExprStmt(_, expr) => hasExprParamUsage(expr) - case _ => false - } + case Assignment(_, _, expr) => hasExprParamUsage(expr) + case ExprStmt(_, expr) => hasExprParamUsage(expr) + case _ => false + } case NestedPathElement(subPath, _) => hasParamUsageAlongPath(Path(subPath.toList), stmts) case _ => false } @@ -715,7 +741,11 @@ class InterproceduralStringAnalysis( npe.element.foreach { nextSubpath => if (!encounteredTarget) { val (_, seen) = findDependeesAcc( - nextSubpath, stmts, target, foundDependees, encounteredTarget + nextSubpath, + stmts, + target, + foundDependees, + encounteredTarget ) encounteredTarget = seen } @@ -736,7 +766,9 @@ class InterproceduralStringAnalysis( * this variable as `ignore`. */ private def findDependentVars( - path: Path, stmts: Array[Stmt[V]], ignore: V + path: Path, + stmts: Array[Stmt[V]], + ignore: V ): mutable.LinkedHashMap[V, Int] = { val dependees = mutable.LinkedHashMap[V, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) @@ -745,7 +777,11 @@ class InterproceduralStringAnalysis( path.elements.foreach { nextSubpath => if (!wasTargetSeen) { val (currentDeps, encounteredTarget) = findDependeesAcc( - nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false + nextSubpath, + stmts, + ignore, + ListBuffer(), + hasTargetBeenSeen = false ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => @@ -871,14 +907,14 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) - final override def uses: Set[PropertyBounds] = Set( + override final 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 = { + override final type InitializationData = InterproceduralStringAnalysis + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { new InterproceduralStringAnalysis(p) } @@ -901,7 +937,9 @@ object LazyInterproceduralStringAnalysis extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( - p: SomeProject, ps: PropertyStore, analysis: InitializationData + p: SomeProject, + ps: PropertyStore, + analysis: InitializationData ): FPCFAnalysis = { val analysis = new InterproceduralStringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) @@ -910,6 +948,6 @@ object LazyInterproceduralStringAnalysis override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - //TODO: Needs TAC key?? + // TODO: Needs TAC key?? override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, FieldAccessInformationKey) -} \ No newline at end of file +} 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 d5b8336779..bbc871fa06 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 @@ -7,6 +7,15 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer + +import org.opalj.br.analyses.ProjectInformationKeys +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.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP @@ -18,14 +27,6 @@ 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.{ProjectInformationKeys, SomeProject} -import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.FPCFAnalysisScheduler -import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -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.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 @@ -36,6 +37,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.value.ValueInformation /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program @@ -195,7 +197,8 @@ 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)) } + state.computedLeanPath, + state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } ).reduce(true) Result(data, StringConstancyProperty(finalSci)) } else { @@ -225,8 +228,12 @@ class IntraproceduralStringAnalysis( )(eps: SomeEPS): ProperPropertyComputationResult = eps match { case FinalP(p) => processFinalP(data, dependees, state, eps.e, p) case InterimLUBP(lb, ub) => InterimResult( - data, lb, ub, dependees.toSet, continuation(data, dependees, state) - ) + data, + lb, + ub, + dependees.toSet, + continuation(data, dependees, state) + ) case _ => throw new IllegalStateException("Could not process the continuation successfully.") } @@ -271,7 +278,11 @@ class IntraproceduralStringAnalysis( npe.element.foreach { nextSubpath => if (!encounteredTarget) { val (_, seen) = findDependeesAcc( - nextSubpath, stmts, target, foundDependees, encounteredTarget + nextSubpath, + stmts, + target, + foundDependees, + encounteredTarget ) encounteredTarget = seen } @@ -292,7 +303,9 @@ class IntraproceduralStringAnalysis( * this variable as `ignore`. */ private def findDependentVars( - path: Path, stmts: Array[Stmt[V]], ignore: V + path: Path, + stmts: Array[Stmt[V]], + ignore: V ): mutable.LinkedHashMap[V, Int] = { val dependees = mutable.LinkedHashMap[V, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) @@ -301,7 +314,11 @@ class IntraproceduralStringAnalysis( path.elements.foreach { nextSubpath => if (!wasTargetSeen) { val (currentDeps, encounteredTarget) = findDependeesAcc( - nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false + nextSubpath, + stmts, + ignore, + ListBuffer(), + hasTargetBeenSeen = false ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => @@ -321,14 +338,14 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) - final override def uses: Set[PropertyBounds] = Set( + override final def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), PropertyBounds.ub(Callees), PropertyBounds.lub(StringConstancyProperty) ) - final override type InitializationData = IntraproceduralStringAnalysis - final override def init(p: SomeProject, ps: PropertyStore): InitializationData = { + override final type InitializationData = IntraproceduralStringAnalysis + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { new IntraproceduralStringAnalysis(p) } @@ -351,7 +368,9 @@ object LazyIntraproceduralStringAnalysis extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( - p: SomeProject, ps: PropertyStore, analysis: InitializationData + p: SomeProject, + ps: PropertyStore, + analysis: InitializationData ): FPCFAnalysis = { val analysis = new IntraproceduralStringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) 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 4f8b82eddf..7dfc20b301 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,20 +8,22 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer + +import org.opalj.br.DefinedMethod +import org.opalj.br.Method +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.NoContext +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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property 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.DefinedMethod -import org.opalj.br.fpcf.properties.{NoContext, StringConstancyProperty} -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.value.ValueInformation /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -76,7 +78,10 @@ abstract class AbstractStringInterpreter( */ protected def getMethodsForPC( implicit - pc: Int, ps: PropertyStore, callees: Callees, typeIt: TypeIterator + pc: Int, + ps: PropertyStore, + callees: Callees, + typeIt: TypeIterator ): (List[Method], Boolean) = { var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() @@ -182,7 +187,6 @@ 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. 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 a7634975b6..d29d6925b8 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 @@ -9,15 +9,15 @@ package interpretation 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.value.ValueInformation 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.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.value.ValueInformation abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { @@ -140,7 +140,8 @@ object InterpretationHandler { * Helper function for [[findDefSiteOfInit]]. */ private def findDefSiteOfInitAcc( - toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + toString: VirtualFunctionCall[V], + stmts: Array[Stmt[V]] ): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder if (toString.name != "toString") { @@ -192,7 +193,7 @@ object InterpretationHandler { 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) + case _ => List(ds) }) } // If no init sites could be determined, use the definition sites of the UVar 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 6ba1c3ef8f..9486ce6c29 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package common -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 -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * 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/common/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala index 3e7dab547b..1d39dd1e47 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package common -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 +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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `DoubleValueInterpreter` is responsible for processing [[DoubleConst]]s. @@ -38,10 +38,13 @@ class DoubleValueInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ))) + 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 001a9af8d0..4855829f93 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package common -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 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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `FloatValueInterpreter` is responsible for processing [[FloatConst]]s. @@ -38,10 +38,13 @@ class FloatValueInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ))) + FinalEP( + 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/common/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala index 2e97cbd076..153eccdf4b 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package common -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.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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. @@ -38,10 +38,13 @@ class IntegerValueInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ))) + 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 aacf979631..fd6e6ff2c3 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 @@ -7,11 +7,11 @@ package string_analysis package interpretation package common +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty 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 /** * The `NewInterpreter` is responsible for processing [[New]] expressions. 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 d65f097880..0394605578 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package common -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.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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `StringConstInterpreter` is responsible for processing [[StringConst]]s. @@ -43,10 +43,13 @@ class StringConstInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value - ))) + 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 d508d2a88c..d700990e4f 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 @@ -9,12 +9,12 @@ package interprocedural import scala.collection.mutable.ListBuffer -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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `ArrayPreparationInterpreter` is responsible for preparing [[ArrayLoad]] as well as @@ -53,7 +53,8 @@ class ArrayPreparationInterpreter( val defSites = instr.arrayRef.asVar.definedBy.toArray val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( - instr, state.tac.stmts + instr, + state.tac.stmts ) allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { @@ -90,7 +91,8 @@ class ArrayPreparationInterpreter( } if (results.isEmpty) { results.append(FinalEP( - (instr.arrayRef.asVar, state.entity._2), StringConstancyProperty(resultSci) + (instr.arrayRef.asVar, state.entity._2), + StringConstancyProperty(resultSci) )) } @@ -124,9 +126,7 @@ object ArrayPreparationInterpreter { // For ArrayStores sortedArrDeclUses.filter { stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int => - allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) - } + } foreach { f: Int => allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) } // For ArrayLoads sortedArrDeclUses.filter { stmts(_) match { 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 514700d191..9196c412c0 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,19 +7,19 @@ package string_analysis package interpretation package interprocedural -import org.opalj.br.analyses.DeclaredFields - 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.PropertyStore + +import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.cg.uVarForDefSites /** @@ -111,12 +111,14 @@ class InterproceduralFieldInterpreter( 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 possibleStrings = "(^null$|" + StringConstancyInformation.UnknownWordSymbol + ")" val sci = StringConstancyInformation( - StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings + StringConstancyLevel.DYNAMIC, + possibleStrings = possibleStrings ) state.appendToFpe2Sci( - defSitEntity, StringConstancyProperty.lb.stringConstancyInformation + defSitEntity, + StringConstancyProperty.lb.stringConstancyInformation ) FinalEP(defSitEntity, StringConstancyProperty(sci)) } else { @@ -128,7 +130,8 @@ class InterproceduralFieldInterpreter( // statement is guaranteed to be executed prior to the field read if (!hasInit) { results.append(FinalEP( - instr, StringConstancyProperty(StringConstancyInformation.getNullElement) + instr, + StringConstancyProperty(StringConstancyInformation.getNullElement) )) } val finalSci = StringConstancyInformation.reduceMultiple(results.map { 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 f14e0f0c83..255b015c83 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,20 +7,19 @@ package string_analysis package interpretation package interprocedural +import org.opalj.ai.ImmediateVMExceptionsOriginOffset +import org.opalj.br.analyses.DeclaredFields +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.cg.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property 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.cg.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.ai.ImmediateVMExceptionsOriginOffset -import org.opalj.br.analyses.DeclaredFields import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter @@ -29,11 +28,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.Integer 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.ArrayLoadFinalizer -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.interpretation.interprocedural.finalizer.GetFieldFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NewArrayFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer +import org.opalj.value.ValueInformation /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -63,7 +63,8 @@ class InterproceduralInterpretationHandler( * @inheritdoc */ override def processDefSite( - defSite: Int, params: List[Seq[StringConstancyInformation]] = List() + defSite: Int, + params: List[Seq[StringConstancyInformation]] = List() ): 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" @@ -71,7 +72,8 @@ class InterproceduralInterpretationHandler( // Function parameters are not evaluated when none are present (this always includes the // implicit parameter for "this" and for exceptions thrown outside the current function) if (defSite < 0 && - (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { + (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) + ) { state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) return FinalEP(e, StringConstancyProperty.lb) } else if (defSite < 0) { @@ -122,15 +124,17 @@ class InterproceduralInterpretationHandler( * [[DoubleConst]]. */ private def processConstExpr( - constExpr: SimpleValueConst, defSite: Int + constExpr: SimpleValueConst, + defSite: Int ): 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) case sc => new StringConstInterpreter(cfg, this).interpret( - sc.asInstanceOf[StringConst], defSite - ) + sc.asInstanceOf[StringConst], + defSite + ) } val sci = finalEP.asFinal.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) @@ -143,10 +147,15 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[ArrayLoad]]s. */ private def processArrayLoad( - expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] + expr: ArrayLoad[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new ArrayPreparationInterpreter( - cfg, this, state, params + cfg, + this, + state, + params ).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation @@ -162,10 +171,15 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NewArray]]s. */ private def processNewArray( - expr: NewArray[V], defSite: Int, params: List[Seq[StringConstancyInformation]] + expr: NewArray[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new NewArrayPreparer( - cfg, this, state, params + cfg, + this, + state, + params ).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation @@ -182,7 +196,8 @@ class InterproceduralInterpretationHandler( */ private def processNew(expr: New, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val finalEP = new NewInterpreter(cfg, this).interpret( - expr, defSite + expr, + defSite ) val sci = finalEP.asFinal.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) @@ -199,7 +214,13 @@ class InterproceduralInterpretationHandler( params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( - cfg, this, ps, state, declaredMethods, params, typeIterator + cfg, + this, + ps, + state, + declaredMethods, + params, + typeIterator ).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 @@ -221,7 +242,8 @@ 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) || !isFinalResult)) { + (state.nonFinalFunctionArgs.contains(expr) || !isFinalResult) + ) { processedDefSites.remove(defSite) } else if (state.nonFinalFunctionArgs.contains(expr) || !isPrepDone) { processedDefSites.remove(defSite) @@ -235,10 +257,18 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] + expr: StaticFunctionCall[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( - cfg, this, ps, state, params, declaredMethods, typeIterator + cfg, + this, + ps, + state, + params, + declaredMethods, + typeIterator ).interpret(expr, defSite) if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -252,7 +282,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[BinaryExpr]]s. */ private def processBinaryExpr( - expr: BinaryExpr[V], defSite: Int + expr: BinaryExpr[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such // expressions @@ -267,10 +298,16 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField( - expr: FieldRead[V], defSite: Int + expr: FieldRead[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( - state, this, ps, fieldAccessInformation, declaredFields, typeIterator + state, + this, + ps, + fieldAccessInformation, + declaredFields, + typeIterator ).interpret(expr, defSite) if (r.isRefinable) { processedDefSites.remove(defSite) @@ -283,10 +320,16 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualFunctionCall( - expr: NonVirtualFunctionCall[V], defSite: Int + expr: NonVirtualFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, typeIterator + cfg, + this, + ps, + state, + declaredMethods, + typeIterator ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -299,10 +342,14 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[VirtualMethodCall]]s. */ def processVirtualMethodCall( - expr: VirtualMethodCall[V], defSite: Int, callees: Callees + expr: VirtualMethodCall[V], + defSite: Int, + callees: Callees ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralVirtualMethodCallInterpreter( - cfg, this, callees + cfg, + this, + callees ).interpret(expr, defSite) doInterimResultHandling(r, defSite) r @@ -312,10 +359,15 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualMethodCall( - nvmc: NonVirtualMethodCall[V], defSite: Int + nvmc: NonVirtualMethodCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( - cfg, this, ps, state, declaredMethods + cfg, + this, + ps, + state, + declaredMethods ).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => @@ -334,7 +386,8 @@ class InterproceduralInterpretationHandler( * results. */ private def doInterimResultHandling( - result: EOptionP[Entity, Property], defSite: Int + result: EOptionP[Entity, Property], + defSite: Int ): Unit = { val sci = if (result.isFinal) { result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation @@ -349,7 +402,8 @@ class InterproceduralInterpretationHandler( * the given list of parameters. Note that `defSite` is required to be <= -2. */ private def getParam( - params: Seq[Seq[StringConstancyInformation]], defSite: Int + params: Seq[Seq[StringConstancyInformation]], + defSite: Int ): StringConstancyInformation = { val paramPos = Math.abs(defSite + 2) if (params.exists(_.length <= paramPos)) { @@ -364,7 +418,8 @@ class InterproceduralInterpretationHandler( * Finalized a given definition state. */ def finalizeDefSite( - defSite: Int, state: InterproceduralComputationState + defSite: Int, + state: InterproceduralComputationState ): Unit = { if (defSite < 0) { state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) @@ -389,8 +444,10 @@ class InterproceduralInterpretationHandler( case ExprStmt(_, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case _ => state.appendToFpe2Sci( - defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true - ) + defSite, + StringConstancyProperty.lb.stringConstancyInformation, + reset = true + ) } } } @@ -411,6 +468,12 @@ object InterproceduralInterpretationHandler { state: InterproceduralComputationState, typeIterator: TypeIterator ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - tac, ps, declaredMethods, declaredFields, fieldAccessInformation, state, typeIterator + tac, + ps, + declaredMethods, + declaredFields, + fieldAccessInformation, + state, + typeIterator ) -} \ 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 8fcc1206d4..a981635d52 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 @@ -7,15 +7,15 @@ package string_analysis package interpretation package interprocedural +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP 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 import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** 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 33f0c7d08a..7c0210d876 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package interprocedural +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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.br.analyses.DeclaredMethods -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.StringConstancyProperty /** * The `InterproceduralNonVirtualMethodCallInterpreter` is responsible for processing @@ -51,7 +51,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[V], defSite: Int + instr: NonVirtualMethodCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { @@ -68,7 +69,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( * these are currently interpreted). */ private def interpretInit( - init: NonVirtualMethodCall[V], defSite: Integer + init: NonVirtualMethodCall[V], + defSite: Integer ): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { case 0 => FinalEP(defSite, StringConstancyProperty.getNeutralElement) @@ -92,7 +94,9 @@ class InterproceduralNonVirtualMethodCallInterpreter( if (r.isFinal) { val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] state.appendToFpe2Sci( - ds, p.stringConstancyInformation, reset = true + ds, + p.stringConstancyInformation, + reset = true ) } 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 186506c70e..175b29ac96 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,15 +8,17 @@ package interpretation package interprocedural import scala.util.Try + +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.NoContext +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.br.analyses.DeclaredMethods -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.{NoContext, StringConstancyProperty} -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** @@ -78,9 +80,7 @@ class InterproceduralStaticFunctionCallInterpreter( interim.get } else { // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r => - r.asFinal.p.asInstanceOf[StringConstancyProperty].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) { @@ -101,10 +101,14 @@ class InterproceduralStaticFunctionCallInterpreter( * This function interprets an arbitrary static function call. */ private def processArbitraryCall( - instr: StaticFunctionCall[V], defSite: Int + instr: StaticFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC( - instr.pc, ps, state.callees, typeIterator + instr.pc, + ps, + state.callees, + typeIterator ) // Static methods cannot be overwritten, thus 1) we do not need the second return value of @@ -121,7 +125,8 @@ class InterproceduralStaticFunctionCallInterpreter( val relevantPCs = directCallSites.filter { case (_, calledMethods) => calledMethods.exists(m => - m.method.name == instr.name && m.method.declaringClassType == instr.declaringClass) + m.method.name == instr.name && m.method.declaringClassType == instr.declaringClass + ) }.keys // Collect all parameters; either from the state if the interpretation of instr was started 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 84a0323504..17a19a8c22 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 @@ -7,15 +7,15 @@ package string_analysis package interpretation package 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.cg.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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `InterproceduralVirtualMethodCallInterpreter` is responsible for processing @@ -53,8 +53,9 @@ class InterproceduralVirtualMethodCallInterpreter( override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.RESET - ) + StringConstancyLevel.CONSTANT, + StringConstancyType.RESET + ) case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) 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 deda8bf0ef..48e996d44a 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 @@ -7,12 +7,12 @@ package string_analysis package interpretation package 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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `NewArrayPreparer` is responsible for preparing [[NewArray]] expressions. @@ -99,4 +99,3 @@ class NewArrayPreparer( } } - 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 ca7a0b6faf..d814fc4774 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 @@ -7,21 +7,22 @@ package string_analysis package interpretation package 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.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.{NoContext, StringConstancyProperty} +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.NoContext +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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** @@ -104,25 +105,29 @@ class VirtualFunctionCallPreparationInterpreter( * finalized later on. */ private def interpretArbitraryCall( - instr: T, defSite: Int + instr: T, + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val (methods, _) = getMethodsForPC( - instr.pc, ps, state.callees, typeIterator + instr.pc, + ps, + state.callees, + typeIterator ) if (methods.isEmpty) { return FinalEP(instr, StringConstancyProperty.lb) } - //TODO: Type Iterator! + // TODO: Type Iterator! val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava val relevantPCs = directCallSites.filter { case (_, calledMethods) => calledMethods.exists { m => - val mClassName = m.method.declaringClassType.toJava - m.method.name == instr.name && mClassName == instrClassName - } + val mClassName = m.method.declaringClassType.toJava + m.method.name == instr.name && mClassName == instrClassName + } }.keys // Collect all parameters; either from the state, if the interpretation of instr was started @@ -196,7 +201,8 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[V], defSite: Int + appendCall: VirtualFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) val appendResult = valueOfAppendCall(appendCall, state) @@ -235,7 +241,8 @@ class VirtualFunctionCallPreparationInterpreter( val receiverSci = StringConstancyInformation.reduceMultiple(receiverScis) StringConstancyInformation( StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, appendSci.constancyLevel + receiverSci.constancyLevel, + appendSci.constancyLevel ), StringConstancyType.APPEND, receiverSci.possibleStrings + appendSci.possibleStrings @@ -257,7 +264,8 @@ class VirtualFunctionCallPreparationInterpreter( * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V], state: InterproceduralComputationState + call: VirtualFunctionCall[V], + state: InterproceduralComputationState ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -290,7 +298,8 @@ class VirtualFunctionCallPreparationInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], state: InterproceduralComputationState + call: VirtualFunctionCall[V], + state: InterproceduralComputationState ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar @@ -334,7 +343,8 @@ 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 && - defSitesValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { + defSitesValueSci.constancyLevel == StringConstancyLevel.CONSTANT + ) { if (defSitesValueSci.isTheNeutralElement) { StringConstancyProperty.lb.stringConstancyInformation } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 8b2918c824..a4e2b9260d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -17,7 +17,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * @author Patrick Mell */ class ArrayLoadFinalizer( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = ArrayLoad[V] @@ -29,7 +30,8 @@ class ArrayLoadFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( - instr, state.tac.stmts + instr, + state.tac.stmts ) allDefSites.foreach { ds => @@ -39,9 +41,7 @@ class ArrayLoadFinalizer( } state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( - allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds => - state.fpe2sci(ds) - } + allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds => state.fpe2sci(ds) } )) } @@ -50,7 +50,8 @@ class ArrayLoadFinalizer( object ArrayLoadFinalizer { def apply( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ): 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 index 02b2cff1e3..1f618fe2e1 100644 --- 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 @@ -14,7 +14,8 @@ import org.opalj.br.cfg.CFG * @author Patrick Mell */ class NewArrayFinalizer( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = NewArray[V] @@ -33,7 +34,8 @@ class NewArrayFinalizer( object NewArrayFinalizer { def apply( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ): NewArrayFinalizer = new NewArrayFinalizer(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 c1c83b3c0e..a37d789916 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 @@ -8,8 +8,8 @@ package interpretation package interprocedural package finalizer -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 /** * @author Patrick Mell @@ -48,4 +48,4 @@ object NonVirtualMethodCallFinalizer { 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/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala index 4faa605ee3..5ea0b2cfcc 100644 --- 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 @@ -8,8 +8,8 @@ package interpretation package interprocedural package finalizer -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 /** * @author Patrick Mell @@ -54,4 +54,4 @@ object StaticFunctionCallFinalizer { state: InterproceduralComputationState ): StaticFunctionCallFinalizer = new StaticFunctionCallFinalizer(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 b8161f5012..09c4fa0e8d 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 @@ -9,16 +9,17 @@ package interprocedural package finalizer 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.br.fpcf.properties.StringConstancyProperty /** * @author Patrick Mell */ class VirtualFunctionCallFinalizer( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = VirtualFunctionCall[V] @@ -34,8 +35,10 @@ class VirtualFunctionCallFinalizer( case "append" => finalizeAppend(instr, defSite) case "toString" => finalizeToString(instr, defSite) case _ => state.appendToFpe2Sci( - defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true - ) + defSite, + StringConstancyProperty.lb.stringConstancyInformation, + reset = true + ) } } @@ -83,7 +86,8 @@ class VirtualFunctionCallFinalizer( } else { StringConstancyInformation( StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, appendSci.constancyLevel + receiverSci.constancyLevel, + appendSci.constancyLevel ), StringConstancyType.APPEND, receiverSci.possibleStrings + appendSci.possibleStrings @@ -116,7 +120,8 @@ class VirtualFunctionCallFinalizer( object VirtualFunctionCallFinalizer { def apply( - state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) } 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 c51277601b..34337d0c7f 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 @@ -9,12 +9,12 @@ package intraprocedural import scala.collection.mutable.ListBuffer -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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `IntraproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as @@ -72,11 +72,14 @@ class IntraproceduralArrayInterpreter( children.append(StringConstancyProperty.lb.stringConstancyInformation) } - FinalEP(instr, StringConstancyProperty( - StringConstancyInformation.reduceMultiple( - children.filter(!_.isTheNeutralElement) + 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 585d125b08..65163a5d90 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 @@ -7,11 +7,11 @@ package string_analysis package interpretation package intraprocedural +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty 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 /** * 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/intraprocedural/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala index 90b284cfb1..e4fe9503e1 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 @@ -7,11 +7,11 @@ package string_analysis package interpretation package intraprocedural +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty 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 /** * The `IntraproceduralGetStaticInterpreter` is responsible for processing @@ -41,4 +41,4 @@ class IntraproceduralGetStaticInterpreter( 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 af5121ec51..0a43050ab0 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 @@ -7,19 +7,19 @@ package string_analysis package interpretation package intraprocedural +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation 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 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.common.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter +import org.opalj.value.ValueInformation /** * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are @@ -42,7 +42,8 @@ class IntraproceduralInterpretationHandler( * @inheritdoc */ override def processDefSite( - defSite: Int, params: List[Seq[StringConstancyInformation]] = List() + defSite: Int, + params: List[Seq[StringConstancyInformation]] = List() ): 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" @@ -70,7 +71,8 @@ class IntraproceduralInterpretationHandler( new NewInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( - cfg, this + cfg, + this ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) @@ -78,13 +80,15 @@ class IntraproceduralInterpretationHandler( new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => new IntraproceduralNonVirtualFunctionCallInterpreter( - cfg, this + cfg, + this ).interpret(expr, defSite) case Assignment(_, _, expr: GetField[V]) => new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( - cfg, this + cfg, + this ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) @@ -92,7 +96,8 @@ class IntraproceduralInterpretationHandler( new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => new IntraproceduralNonVirtualMethodCallInterpreter( - cfg, this + cfg, + this ).interpret(nvmc, defSite) case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } 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 15c8ae799f..737191e5f8 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 @@ -7,11 +7,11 @@ package string_analysis package interpretation package intraprocedural +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty 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 /** * The `IntraproceduralNonVirtualFunctionCallInterpreter` is responsible for processing 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 96cb230940..8cae654391 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 @@ -9,12 +9,12 @@ package intraprocedural 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.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 /** * The `IntraproceduralNonVirtualMethodCallInterpreter` is responsible for processing @@ -50,7 +50,8 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[V], defSite: Int + instr: NonVirtualMethodCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { case "" => interpretInit(instr) 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 b8cfd2b6a5..b57243f9d7 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 @@ -7,11 +7,11 @@ package string_analysis package interpretation package intraprocedural +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty 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 /** * The `IntraproceduralStaticFunctionCallInterpreter` is responsible for processing 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 990511ea50..025a251032 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 @@ -7,20 +7,20 @@ package string_analysis package interpretation package intraprocedural -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.ComputationalTypeDouble import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt +import org.opalj.br.DoubleType +import org.opalj.br.FloatType import org.opalj.br.ObjectType +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.br.ComputationalTypeDouble -import org.opalj.br.DoubleType -import org.opalj.br.FloatType +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `IntraproceduralVirtualFunctionCallInterpreter` is responsible for processing @@ -115,7 +115,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( else { StringConstancyInformation( StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, appendSci.constancyLevel + receiverSci.constancyLevel, + appendSci.constancyLevel ), StringConstancyType.APPEND, receiverSci.possibleStrings + appendSci.possibleStrings @@ -137,7 +138,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( val r = exprHandler.processDefSite(ds) r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }.filter { sci => !sci.isTheNeutralElement } - val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement else + val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement + else scis.head StringConstancyProperty(sci) } @@ -170,7 +172,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( // 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.constancyLevel == StringConstancyLevel.CONSTANT + ) { sci.copy( possibleStrings = sci.possibleStrings.toInt.toChar.toString ) 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 a18a8ddcee..9a7c994a8c 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 @@ -7,14 +7,14 @@ package string_analysis package interpretation package intraprocedural -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.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.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP /** * The `IntraproceduralVirtualMethodCallInterpreter` is responsible for processing @@ -53,8 +53,9 @@ class IntraproceduralVirtualMethodCallInterpreter( override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.RESET - ) + StringConstancyLevel.CONSTANT, + StringConstancyType.RESET + ) case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) 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 b37843c9b4..45c6aeffb2 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 @@ -8,6 +8,7 @@ package preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG @@ -62,7 +63,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * whole conditional as described above. */ private def getStartAndEndIndexOfCondWithAlternative( - branchingSite: Int, processedIfs: mutable.Map[Int, Unit] + branchingSite: Int, + processedIfs: mutable.Map[Int, Unit] ): (Int, Int) = { processedIfs(branchingSite) = () @@ -73,7 +75,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val nextBlock = cfg.bb(popped).successors.map { case bb: BasicBlock => bb.startPC // Handle Catch Nodes? - case _ => -1 + case _ => -1 }.max var containsIf = false for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { @@ -95,9 +97,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 - } + isRelevantGoto = !cfg.findNaturalLoops().exists { l => l.head == gotoSite } Some(goto) case _ => None } @@ -125,7 +125,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { endSite = nextBlock } case _ => - endSite = if (nextBlock > branchingSite) nextBlock - 1 else + endSite = if (nextBlock > branchingSite) nextBlock - 1 + else cfg.findNaturalLoops().find { _.head == goto.targetStmt }.get.last @@ -144,7 +145,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } i += 1 } - endSite = if (nextIf.isDefined) nextIf.get.targetStmt else { + endSite = if (nextIf.isDefined) nextIf.get.targetStmt + else { stack.clear() i - 1 } @@ -175,14 +177,15 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * whole conditional as described above. */ private def getStartAndEndIndexOfCondWithoutAlternative( - branchingSite: Int, processedIfs: mutable.Map[Int, Unit] + branchingSite: Int, + processedIfs: mutable.Map[Int, Unit] ): (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 + case _ => -1 }.max var nextIfIndex = -1 @@ -199,7 +202,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (nextIfIndex > -1 && !isHeadOfLoop(nextIfIndex, cfg.findNaturalLoops(), cfg)) { processedIfs(nextIfIndex) = () val (_, newEndIndex) = getStartAndEndIndexOfCondWithoutAlternative( - nextIfIndex, processedIfs + nextIfIndex, + processedIfs ) endIndex = newEndIndex } @@ -229,9 +233,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 - } + val inTryBlocks = cfg.catchNodes.filter { cn => branchingSite >= cn.startPC && branchingSite <= cn.endPC } if (inTryBlocks.nonEmpty) { val tryEndPC = inTryBlocks.minBy(-_.startPC).endPC if (endIndex > tryEndPC) { @@ -307,7 +309,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (!hasOnlyFinally) { if (isThrowable) { val throwFinally = cfg.catchNodes.find(_.startPC == cn.handlerPC) - val endIndex = if (throwFinally.isDefined) throwFinally.get.endPC - 1 else + 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 @@ -346,7 +349,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { tryInfo(cn.startPC) = endOfFinally - 1 - numElementsFinally } else { val blockIndex = if (cnSameStartPC.head.endPC < 0) - cfg.code.instructions.length - 1 else cnSameStartPC.head.endPC + cfg.code.instructions.length - 1 + else cnSameStartPC.head.endPC tryInfo(cn.startPC) = cfg.bb(blockIndex).successors.map { case bb: BasicBlock => bb.startPC case _ => blockIndex @@ -381,7 +385,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { HierarchicalCSOrder(List((Some(element), List()))) } else { HierarchicalCSOrder(List(( - Some(element), children(element).map { buildHierarchy(_, children) }.toList + Some(element), + children(element).map { buildHierarchy(_, children) }.toList ))) } } @@ -395,7 +400,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * of the tuple `(start, end)`. */ private def buildRepetitionPath( - start: Int, end: Int, fill: Boolean + start: Int, + end: Int, + fill: Boolean ): (Path, List[(Int, Int)]) = { val path = ListBuffer[SubPath]() if (fill) { @@ -419,7 +426,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * the second from 11 to 20. */ private def buildCondPath( - start: Int, end: Int, pathType: NestedPathType.Value, fill: Boolean + 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 @@ -435,13 +445,14 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var nextBlock = cfg.bb(popped).successors.map { case bb: BasicBlock => bb.startPC // Handle Catch Nodes? - case _ => -1 + case _ => -1 }.max if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { nextBlock = popped + 1 while (nextBlock < cfg.code.instructions.length - 1 && - !cfg.code.instructions(nextBlock).isInstanceOf[If[V]]) { + !cfg.code.instructions(nextBlock).isInstanceOf[If[V]] + ) { nextBlock += 1 } } @@ -488,8 +499,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { subPaths.append(NestedPathElement(subpathElements, None)) } - val pathTypeToUse = if (pathType == NestedPathType.CondWithAlternative && - startEndPairs.length == 1) NestedPathType.CondWithoutAlternative else pathType + val pathTypeToUse = + if (pathType == NestedPathType.CondWithAlternative && + startEndPairs.length == 1 + ) NestedPathType.CondWithoutAlternative + else pathType (Path(List(NestedPathElement(subPaths, Some(pathTypeToUse)))), startEndPairs.toList) } @@ -499,7 +513,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * statements and that it determines itself whether the switch contains a default case or not. */ private def buildPathForSwitch( - start: Int, end: Int, fill: Boolean + start: Int, + end: Int, + fill: Boolean ): (Path, List[(Int, Int)]) = { val startEndPairs = ListBuffer[(Int, Int)]() val switch = cfg.code.instructions(start).asSwitch @@ -509,7 +525,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (containsDefault) { caseStmts.append(switch.defaultStmt) } - val pathType = if (containsDefault) NestedPathType.CondWithAlternative else + val pathType = if (containsDefault) NestedPathType.CondWithAlternative + else NestedPathType.CondWithoutAlternative var previousStart = caseStmts.head @@ -549,7 +566,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @note This function has basic / primitive support for `throwable`s. */ private def buildTryCatchPath( - start: Int, end: Int, fill: Boolean + 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)]() @@ -654,9 +673,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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) - }) + subPaths.appendAll((startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map { i => FlatPathElement(i) }) } ( @@ -689,7 +706,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * or function call not related to the loop header for instance). */ protected def isHeadOfLoop( - site: Int, loops: List[List[Int]], cfg: CFG[Stmt[V], TACStmts[V]] + site: Int, + loops: List[List[Int]], + cfg: CFG[Stmt[V], TACStmts[V]] ): Boolean = { var belongsToLoopHeader = false @@ -745,8 +764,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 @@ -781,7 +800,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 @@ -819,7 +838,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val from = toVisitStack.pop() val to = from.successors if ((from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) && - from.nodeId >= branchingSite) { + from.nodeId >= branchingSite + ) { reachableCount += 1 } seenNodes.append(from) @@ -846,11 +866,13 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @note This function assumes that `from` >= 0! */ protected def doesPathExistTo( - from: Int, to: Int, alreadySeen: List[Int] = List() + from: Int, + to: Int, + alreadySeen: List[Int] = List() ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_)= ()) + alreadySeen.foreach(seenNodes(_) = ()) seenNodes(from) = () while (stack.nonEmpty) { @@ -897,7 +919,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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))) + isHeadOfLoop(index, List(nextLoop), cfg) || isEndOfLoop(index, List(nextLoop)) + ) if (relevantLoop.nonEmpty) { startIndex = relevantLoop.head.head endIndex = relevantLoop.head.last @@ -920,7 +943,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[getStartAndEndIndexOfCondWithoutAlternative]], and [[determineTryCatchBounds]]. */ protected def processIf( - stmt: Int, processedIfs: mutable.Map[Int, Unit] + stmt: Int, + processedIfs: mutable.Map[Int, Unit] ): CSInfo = { val csType = determineTypeOfIf(stmt, processedIfs) val (startIndex, endIndex) = csType match { @@ -970,7 +994,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } val containsDefault = caseStmts.length == caseStmts.distinct.length - val pathType = if (containsDefault) NestedPathType.CondWithAlternative else + val pathType = if (containsDefault) NestedPathType.CondWithAlternative + else NestedPathType.CondWithoutAlternative (stmt, end, pathType) @@ -984,7 +1009,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * statement). */ protected def determineTypeOfIf( - stmtIndex: Int, processedIfs: mutable.Map[Int, Unit] + stmtIndex: Int, + processedIfs: mutable.Map[Int, Unit] ): NestedPathType.Value = { // Is the first condition enough to identify loops? val loops = cfg.findNaturalLoops() @@ -1045,8 +1071,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (next.nodeId == endSite) { val doesPathExist = stack.filter(_.nodeId >= 0).foldLeft(false) { - (doesExist: Boolean, next: CFGNode) => - doesExist || doesPathExistTo(next.nodeId, endSite) + (doesExist: Boolean, next: CFGNode) => doesExist || doesPathExistTo(next.nodeId, endSite) } // In case no more path exists, clear the stack which (=> no more iterations) if (!doesPathExist) { @@ -1095,7 +1120,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[buildPathForSwitch]], and [[buildTryCatchPath]]. */ protected def buildPathForElement( - toTransform: HierarchicalCSOrder, fill: Boolean + toTransform: HierarchicalCSOrder, + fill: Boolean ): (Path, List[(Int, Int)]) = { val element = toTransform.hierarchy.head._1.get val start = element._1 @@ -1163,7 +1189,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { childrenOf.foreach { nextCS => mapChildrenOf(nextCS._1) = nextCS._2 } HierarchicalCSOrder(List(( - None, cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf)) + None, + cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf)) ))) } @@ -1183,7 +1210,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @return Returns the transformed [[Path]]. */ protected def hierarchyToPath( - topElements: List[HierarchicalCSOrder], startIndex: Int, endIndex: Int + 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 @@ -1194,9 +1223,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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)) - } + indexLastCSEnd.until(nextCSStart).foreach { i => finalPath.append(FlatPathElement(i)) } val children = nextTopEle.hierarchy.head._2 if (children.isEmpty) { @@ -1232,10 +1259,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case fpe: FlatPathElement => fpe.element case inner: NestedPathElement => Path.getLastElementInNPE(inner).element // Compiler wants it but should never be the case! - case _ => -1 + case _ => -1 } if (insertIndex < startEndPairs.length && - lastInsertedIndex >= startEndPairs(insertIndex)._2) { + lastInsertedIndex >= startEndPairs(insertIndex)._2 + ) { insertIndex += 1 } } @@ -1266,7 +1294,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { subPathNpe.element.filter { case npe: NestedPathElement => npe.element.nonEmpty case _ => true - }, subPathNpe.elementType + }, + subPathNpe.elementType ) finalPath.append(subPathToAdd) } 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 18a4cd4f64..fbbf9fb5a7 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 @@ -9,8 +9,8 @@ package preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.value.ValueInformation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.value.ValueInformation /** * @author Patrick Mell @@ -82,7 +82,8 @@ case class Path(elements: List[SubPath]) { * sorted list. */ private def getAllDefAndUseSites( - obj: DUVar[ValueInformation], stmts: Array[Stmt[V]] + obj: DUVar[ValueInformation], + stmts: Array[Stmt[V]] ): List[Int] = { val defAndUses = ListBuffer[Int]() val stack = mutable.Stack[Int](obj.definedBy.toList: _*) @@ -119,7 +120,7 @@ case class Path(elements: List[SubPath]) { 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 + case _ => false }) } } @@ -147,7 +148,8 @@ case class Path(elements: List[SubPath]) { * well. */ private def stripUnnecessaryBranches( - npe: NestedPathElement, endSite: Int + npe: NestedPathElement, + endSite: Int ): NestedPathElement = { npe.element.foreach { case innerNpe: NestedPathElement => @@ -189,13 +191,13 @@ case class Path(elements: List[SubPath]) { toProcess: NestedPathElement, siteMap: Map[Int, Unit], endSite: Int, - includeAlternatives: Boolean = false + 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.elementType.get == NestedPathType.TryCatchFinally) toProcess.element.foreach { next => // The stop flag is used to make sure that within a sub-path only the elements up to the @@ -212,7 +214,10 @@ case class Path(elements: List[SubPath]) { } case npe: NestedPathElement if isTryCatch => val (leanedSubPath, _) = makeLeanPathAcc( - npe, siteMap, endSite, includeAlternatives = true + npe, + siteMap, + endSite, + includeAlternatives = true ) if (leanedSubPath.isDefined) { elements.append(leanedSubPath.get) @@ -220,7 +225,9 @@ case class Path(elements: List[SubPath]) { case npe: NestedPathElement => if (!hasTargetBeenSeen) { val (leanedSubPath, wasTargetSeen) = makeLeanPathAcc( - npe, siteMap, endSite + npe, + siteMap, + endSite ) if (leanedSubPath.isDefined) { elements.append(leanedSubPath.get) @@ -288,7 +295,8 @@ case class Path(elements: List[SubPath]) { case npe: NestedPathElement => val (leanedPath, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) if (npe.elementType.isDefined && - npe.elementType.get != NestedPathType.TryCatchFinally) { + npe.elementType.get != NestedPathType.TryCatchFinally + ) { reachedEndSite = wasTargetSeen } if (leanedPath.isDefined) { @@ -304,8 +312,9 @@ case class Path(elements: List[SubPath]) { // body in any case (as there is no alternative branch to consider) if (leanPath.tail.isEmpty) { leanPath.head match { - case npe: NestedPathElement if npe.elementType.get == NestedPathType.Repetition || - npe.element.tail.isEmpty => + case npe: NestedPathElement + if npe.elementType.get == NestedPathType.Repetition || + npe.element.tail.isEmpty => leanPath = removeOuterBranching(npe) case _ => } @@ -313,8 +322,9 @@ 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) leanPath.last match { - case npe: NestedPathElement if npe.elementType.isDefined && - (npe.elementType.get != NestedPathType.TryCatchFinally) => + 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) 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 299b5d40ae..cd273ce220 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 @@ -8,6 +8,8 @@ package preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map + +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -15,7 +17,6 @@ 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.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -36,7 +37,8 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * Accumulator function for transforming a path into a StringTree element. */ private def pathToTreeAcc( - subpath: SubPath, fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] + subpath: SubPath, + fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] ): Option[StringTree] = { subpath match { case fpe: FlatPathElement => @@ -73,13 +75,14 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { npe.elementType.get match { case NestedPathType.Repetition => val processedSubPath = pathToStringTree( - Path(npe.element.toList), fpe2Sci, resetExprHandler = false + Path(npe.element.toList), + fpe2Sci, + resetExprHandler = false ) Some(StringTreeRepetition(processedSubPath)) case _ => - val processedSubPaths = npe.element.map { ne => - pathToTreeAcc(ne, fpe2Sci) - }.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 | @@ -105,9 +108,8 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { case 0 => None case 1 => pathToTreeAcc(npe.element.head, fpe2Sci) case _ => - val processed = npe.element.map { ne => - pathToTreeAcc(ne, fpe2Sci) - }.filter(_.isDefined).map(_.get) + val processed = + npe.element.map { ne => pathToTreeAcc(ne, fpe2Sci) }.filter(_.isDefined).map(_.get) if (processed.isEmpty) { None } else { 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 b3cc24c894..70c9c7b80d 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 @@ -7,11 +7,11 @@ package analyses import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.value.ValueInformation -import org.opalj.br.Method -import org.opalj.br.fpcf.properties.StringConstancyProperty /** * @author Patrick Mell From c3b9715575a51b44bd97184b8acd8fd69c29af7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 25 Jan 2024 13:07:18 +0100 Subject: [PATCH 331/583] Create new PDUVar class --- .../src/main/scala/org/opalj/tac/DUVar.scala | 12 ++- .../src/main/scala/org/opalj/tac/PDUVar.scala | 73 +++++++++++++++++++ .../opalj/tac/fpcf/analyses/cg/package.scala | 46 ++---------- .../pointsto/AbstractPointsToAnalysis.scala | 1 - .../main/scala/org/opalj/tac/package.scala | 39 ++++++++++ 5 files changed, 128 insertions(+), 43 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala index e7c6b929df..2d0ef12092 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala @@ -45,6 +45,8 @@ abstract class DUVar[+Value <: ValueInformation] extends Var[DUVar[Value]] { */ def definedBy: IntTrieSet + def toPersistentForm(implicit stmts: Array[Stmt[V]]): PDUVar[Value] + override def toCanonicalForm( implicit ev: DUVar[Value] <:< DUVar[ValueInformation] ): DUVar[ValueInformation] @@ -183,6 +185,8 @@ class DVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ s"DVar(useSites=${useSites.mkString("{", ",", "}")},value=$value,origin=$origin)" } + override def toPersistentForm(implicit stmts: Array[Stmt[V]]): Nothing = throw new UnsupportedOperationException + } object DVar { @@ -269,6 +273,10 @@ class UVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ s"UVar(defSites=${defSites.mkString("{", ",", "}")},value=$value)" } + override def toPersistentForm( + implicit stmts: Array[Stmt[V]] + ): PUVar[Value] = PUVar(value, definedBy.map(pcOfDefSite _)) + } object UVar { @@ -282,9 +290,7 @@ object UVar { new UVar[d.DomainValue](value, defSites) } - def apply(value: ValueInformation, defSites: IntTrieSet): UVar[ValueInformation] = { - new UVar(value, defSites) - } + def apply[Value <: ValueInformation](value: Value, defSites: IntTrieSet): UVar[Value] = new UVar(value, defSites) def unapply[Value <: ValueInformation /* org.opalj.ai.ValuesDomain#DomainValue*/ ]( u: UVar[Value] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala new file mode 100644 index 0000000000..e9f5465b12 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala @@ -0,0 +1,73 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac + +import org.opalj.ai.PCs +import org.opalj.collection.immutable.IntTrieSet +import org.opalj.value.ValueInformation + +/** + * A persistent (i.e. TAC independent) representation of a [[DUVar]]. + */ +abstract class PDUVar[+Value <: ValueInformation] { + + /** + * The information about the variable that were derived by the underlying data-flow analysis. + */ + def value: Value + + /** + * The PCs of the instructions which use this variable. + * + * '''Defined, if and only if this is an assignment statement.''' + */ + def usePCs: PCs + + /** + * The PCs of the instructions which initialize this variable/ + * the origin of the value identifies the expression which initialized this variable. + * + * '''Defined, if and only if this is a variable usage.''' + * + * For more information see [[DUVar.definedBy]] + */ + def defPCs: PCs + + def toValueOriginForm(implicit pcToIndex: Array[Int]): DUVar[Value] +} + +class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( + val value: Value, + val defPCs: PCs +) extends PDUVar[Value] { + + def usePCs: Nothing = throw new UnsupportedOperationException + + override def equals(other: Any): Boolean = { + other match { + case that: PUVar[_] => this.defPCs == that.defPCs + case _ => false + } + } + + override def toString: String = { + s"PUVar(defSites=${defPCs.mkString("{", ",", "}")},value=$value)" + } + + override def toValueOriginForm( + implicit pcToIndex: Array[Int] + ): UVar[Value] = UVar(value, valueOriginsOfPCs(defPCs, pcToIndex)) +} + +object PUVar { + + def apply(d: org.opalj.ai.ValuesDomain)( + value: d.DomainValue, + defPCs: PCs + ): PUVar[d.DomainValue] = { + new PUVar[d.DomainValue](value, defPCs) + } + + def apply[Value <: ValueInformation](value: Value, defSites: IntTrieSet): PUVar[Value] = new PUVar(value, defSites) + + def unapply[Value <: ValueInformation](u: UVar[Value]): Some[(Value, IntTrieSet)] = Some((u.value, u.defSites)) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/package.scala index 493d4537f0..a2590d47d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/package.scala @@ -4,19 +4,9 @@ package tac package fpcf package analyses -import org.opalj.ai.ImmediateVMExceptionsOriginOffset -import org.opalj.ai.MethodExternalExceptionsOriginOffset -import org.opalj.ai.ValueOrigin -import org.opalj.ai.ValueOriginForImmediateVMException -import org.opalj.ai.ValueOriginForMethodExternalException -import org.opalj.ai.isImmediateVMException -import org.opalj.ai.isMethodExternalExceptionOrigin -import org.opalj.ai.pcOfImmediateVMException -import org.opalj.ai.pcOfMethodExternalException import org.opalj.br.ComputationalTypeReference import org.opalj.br.DeclaredMethod import org.opalj.br.ObjectType -import org.opalj.br.PCs import org.opalj.br.ReferenceType import org.opalj.br.instructions.ACONST_NULL import org.opalj.br.instructions.LoadClass @@ -30,7 +20,6 @@ import org.opalj.br.instructions.LoadMethodType import org.opalj.br.instructions.LoadMethodType_W import org.opalj.br.instructions.LoadString import org.opalj.br.instructions.LoadString_W -import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.collection.immutable.UIDSet import org.opalj.log.LogContext @@ -42,6 +31,8 @@ package object cg { /** * A persistent representation (using pcs instead of TAC value origins) for a UVar. + * + * @deprecated Use [[UVar.toPersistentForm]] (available through the type [[V]]) instead. */ final def persistentUVar( value: V @@ -51,34 +42,11 @@ package object cg { Some((value.value, value.definedBy.map(pcOfDefSite _))) } - final def pcOfDefSite(valueOrigin: ValueOrigin)(implicit stmts: Array[Stmt[V]]): Int = { - if (valueOrigin >= 0) - stmts(valueOrigin).pc - else if (valueOrigin > ImmediateVMExceptionsOriginOffset) - valueOrigin // <- it is a parameter! - else if (valueOrigin > MethodExternalExceptionsOriginOffset) - ValueOriginForImmediateVMException(stmts(pcOfImmediateVMException(valueOrigin)).pc) - else - ValueOriginForMethodExternalException( - stmts(pcOfMethodExternalException(valueOrigin)).pc - ) - } - - final def valueOriginsOfPCs(pcs: PCs, pcToIndex: Array[Int]): IntTrieSet = { - pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => - if (ai.underlyingPC(pc) < 0) - origins + pc // parameter - else if (pc >= 0 && pcToIndex(pc) >= 0) - origins + pcToIndex(pc) // local - else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) - origins + ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc))) - else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) - origins + ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc))) - else - origins // as is - } - } - + /** + * Regaining a non-persistent (using TAC value origins) form of the UVar. + * + * @deprecated Use [[PUVar.toValueOriginForm]] instead. + */ final def uVarForDefSites( defSites: (ValueInformation, IntTrieSet), pcToIndex: Array[Int] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AbstractPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AbstractPointsToAnalysis.scala index 5ded285532..2a8c5efedd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AbstractPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AbstractPointsToAnalysis.scala @@ -52,7 +52,6 @@ import org.opalj.log.OPALLogger.logOnce import org.opalj.log.Warn import org.opalj.tac.common.DefinitionSite import org.opalj.tac.fpcf.analyses.cg.ReachableMethodAnalysis -import org.opalj.tac.fpcf.analyses.cg.valueOriginsOfPCs import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.IsMultipleReferenceValue import org.opalj.value.IsReferenceValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala index 229f24ef11..9e7209e21d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala @@ -3,11 +3,22 @@ package org.opalj import org.opalj.ai.AIResult import org.opalj.ai.Domain +import org.opalj.ai.ImmediateVMExceptionsOriginOffset +import org.opalj.ai.MethodExternalExceptionsOriginOffset +import org.opalj.ai.ValueOrigin +import org.opalj.ai.ValueOriginForImmediateVMException +import org.opalj.ai.ValueOriginForMethodExternalException import org.opalj.ai.domain.RecordDefUse +import org.opalj.ai.isImmediateVMException +import org.opalj.ai.isMethodExternalExceptionOrigin +import org.opalj.ai.pcOfImmediateVMException +import org.opalj.ai.pcOfMethodExternalException import org.opalj.br.ExceptionHandler import org.opalj.br.ExceptionHandlers +import org.opalj.br.PCs 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.graphs.Node import org.opalj.value.ValueInformation @@ -21,6 +32,34 @@ package object tac { type V = DUVar[ValueInformation] + final def pcOfDefSite(valueOrigin: ValueOrigin)(implicit stmts: Array[Stmt[V]]): Int = { + if (valueOrigin >= 0) + stmts(valueOrigin).pc + else if (valueOrigin > ImmediateVMExceptionsOriginOffset) + valueOrigin // <- it is a parameter! + else if (valueOrigin > MethodExternalExceptionsOriginOffset) + ValueOriginForImmediateVMException(stmts(pcOfImmediateVMException(valueOrigin)).pc) + else + ValueOriginForMethodExternalException( + stmts(pcOfMethodExternalException(valueOrigin)).pc + ) + } + + final def valueOriginsOfPCs(pcs: PCs, pcToIndex: Array[Int]): IntTrieSet = { + pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => + if (ai.underlyingPC(pc) < 0) + origins + pc // parameter + else if (pc >= 0 && pcToIndex(pc) >= 0) + origins + pcToIndex(pc) // local + else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) + origins + ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc))) + else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) + origins + ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc))) + else + origins // as is + } + } + /** * Identifies the implicit `this` reference in the 3-address code representation. * -1 always identifies the origin of the self reference(`this`) if the the method is From f7a4abae3f6f756a9a6611e0020e9b01a9b65d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 25 Jan 2024 13:12:20 +0100 Subject: [PATCH 332/583] Rename entity and context types --- .../info/StringAnalysisReflectiveCalls.scala | 30 +++---- .../org/opalj/fpcf/StringAnalysisTest.scala | 20 ++--- .../InterproceduralComputationState.scala | 18 ++-- .../InterproceduralStringAnalysis.scala | 72 ++++++++-------- .../IntraproceduralStringAnalysis.scala | 58 ++++++------- .../AbstractStringInterpreter.scala | 26 +++--- .../InterpretationHandler.scala | 28 +++--- .../common/BinaryExprInterpreter.scala | 6 +- .../common/DoubleValueInterpreter.scala | 4 +- .../common/FloatValueInterpreter.scala | 4 +- .../common/IntegerValueInterpreter.scala | 4 +- .../common/NewInterpreter.scala | 4 +- .../common/StringConstInterpreter.scala | 4 +- .../ArrayPreparationInterpreter.scala | 18 ++-- .../InterproceduralFieldInterpreter.scala | 2 +- ...InterproceduralInterpretationHandler.scala | 86 +++++++++---------- ...ralNonVirtualFunctionCallInterpreter.scala | 18 ++-- ...duralNonVirtualMethodCallInterpreter.scala | 20 ++--- ...ceduralStaticFunctionCallInterpreter.scala | 30 +++---- ...oceduralVirtualMethodCallInterpreter.scala | 8 +- .../interprocedural/NewArrayPreparer.scala | 12 +-- ...alFunctionCallPreparationInterpreter.scala | 38 ++++---- .../finalizer/ArrayLoadFinalizer.scala | 6 +- .../finalizer/GetFieldFinalizer.scala | 2 +- .../finalizer/NewArrayFinalizer.scala | 6 +- .../NonVirtualMethodCallFinalizer.scala | 2 +- .../StaticFunctionCallFinalizer.scala | 2 +- .../VirtualFunctionCallFinalizer.scala | 6 +- .../IntraproceduralArrayInterpreter.scala | 10 +-- .../IntraproceduralFieldInterpreter.scala | 6 +- .../IntraproceduralGetStaticInterpreter.scala | 4 +- ...IntraproceduralInterpretationHandler.scala | 20 ++--- ...ralNonVirtualFunctionCallInterpreter.scala | 6 +- ...duralNonVirtualMethodCallInterpreter.scala | 12 +-- ...ceduralStaticFunctionCallInterpreter.scala | 6 +- ...eduralVirtualFunctionCallInterpreter.scala | 16 ++-- ...oceduralVirtualMethodCallInterpreter.scala | 6 +- .../preprocessing/AbstractPathFinder.scala | 42 ++++----- .../preprocessing/DefaultPathFinder.scala | 2 +- .../string_analysis/preprocessing/Path.scala | 12 +-- .../preprocessing/WindowPathFinder.scala | 6 +- .../string_analysis/string_analysis.scala | 16 ++-- 42 files changed, 349 insertions(+), 349 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 5d7675b30d..6b3a5b8fe7 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 @@ -39,8 +39,8 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis 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.string_analysis.SContext +import org.opalj.tac.fpcf.analyses.string_analysis.SEntity import org.opalj.tac.fpcf.properties.TACAI /** @@ -107,7 +107,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * analysis and the second element corresponds to the method name in which the entity occurred, * i.e., a value in [[relevantMethodNames]]. */ - private val entityContext = ListBuffer[(P, String)]() + private val entityContext = ListBuffer[(SContext, String)]() /** * A list of fully-qualified method names that are to be skipped, e.g., because they make an @@ -178,10 +178,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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 + ps: PropertyStore, + method: Method, + call: Call[SEntity], + resultMap: ResultMapType ): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) @@ -233,25 +233,25 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } private def processStatements( - ps: PropertyStore, - stmts: Array[Stmt[V]], - m: Method, - resultMap: ResultMapType + ps: PropertyStore, + stmts: Array[Stmt[SEntity]], + 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]) => + case Assignment(_, _, c: StaticFunctionCall[SEntity]) => processFunctionCall(ps, m, c, resultMap) - case Assignment(_, _, c: VirtualFunctionCall[V]) => + case Assignment(_, _, c: VirtualFunctionCall[SEntity]) => processFunctionCall(ps, m, c, resultMap) case _ => } case ExprStmt.ASTID => stmt match { - case ExprStmt(_, c: StaticFunctionCall[V]) => + case ExprStmt(_, c: StaticFunctionCall[SEntity]) => processFunctionCall(ps, m, c, resultMap) - case ExprStmt(_, c: VirtualFunctionCall[V]) => + case ExprStmt(_, c: VirtualFunctionCall[SEntity]) => processFunctionCall(ps, m, c, resultMap) case _ => } 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 ec3dff1885..7b4b748233 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 @@ -22,7 +22,7 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.SEntity /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -64,8 +64,8 @@ sealed class StringAnalysisTestRunner( def determineEntitiesToAnalyze( project: Project[URL] - ): Iterable[(V, Method)] = { - val entitiesToAnalyze = ListBuffer[(V, Method)]() + ): Iterable[(SEntity, Method)] = { + val entitiesToAnalyze = ListBuffer[(SEntity, Method)]() val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => @@ -82,9 +82,9 @@ sealed class StringAnalysisTestRunner( } def determineEAS( - entities: Iterable[(V, Method)], - project: Project[URL] - ): Iterable[((V, Method), String => String, List[Annotation])] = { + entities: Iterable[(SEntity, Method)], + project: Project[URL] + ): Iterable[((SEntity, 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 @@ -128,10 +128,10 @@ object StringAnalysisTestRunner { * order in which they occurred in the given statements. */ def extractUVars( - cfg: CFG[Stmt[V], TACStmts[V]], - fqTestMethodsClass: String, - nameTestMethod: String - ): List[V] = { + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + fqTestMethodsClass: String, + nameTestMethod: String + ): List[SEntity] = { cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) => declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod 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 fef66a91f1..c8e13afd2b 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 @@ -29,7 +29,7 @@ import org.opalj.value.ValueInformation * @param fieldWriteThreshold See the documentation of * [[InterproceduralStringAnalysis#fieldWriteThreshold]]. */ -case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldWriteThreshold: Int = 100) { +case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, fieldWriteThreshold: Int = 100) { /** * The Three-Address Code of the entity's method */ @@ -70,7 +70,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW /** * A mapping from DUVar elements to the corresponding indices of the FlatPathElements */ - val var2IndexMapping: mutable.Map[V, ListBuffer[Int]] = mutable.Map() + val var2IndexMapping: mutable.Map[SEntity, ListBuffer[Int]] = mutable.Map() /** * A mapping from values / indices of FlatPathElements to StringConstancyInformation @@ -88,7 +88,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW * [[interimFpe2sci]]. For a discussion of the necessity, see the documentation of * [[interimFpe2sci]]. */ - private val entity2lastInterimFpe2SciValue: mutable.Map[V, StringConstancyInformation] = + private val entity2lastInterimFpe2SciValue: mutable.Map[SEntity, StringConstancyInformation] = mutable.Map() /** @@ -110,7 +110,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW * 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() + val paramResultPositions: mutable.Map[SContext, (Int, Int)] = mutable.Map() /** * Parameter values of a method / function. The structure of this field is as follows: Each item @@ -132,13 +132,13 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW * 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() + val nonFinalFunctionArgs: mutable.Map[FunctionCall[SEntity], 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, ListBuffer[FunctionCall[V]]] = mutable.Map() + val entity2Function: mutable.Map[SContext, ListBuffer[FunctionCall[SEntity]]] = mutable.Map() /** * A mapping from a method to definition sites which indicates that a method is still prepared, @@ -150,7 +150,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW /** * A mapping which indicates whether a virtual function call is fully prepared. */ - val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[V], Boolean] = mutable.Map() + val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[SEntity], Boolean] = mutable.Map() /** * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] @@ -198,7 +198,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW def appendToInterimFpe2Sci( defSite: Int, sci: StringConstancyInformation, - entity: Option[V] = None + entity: Option[SEntity] = None ): Unit = { val numElements = var2IndexMapping.values.flatten.count(_ == defSite) var addedNewList = false @@ -229,7 +229,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: P, fieldW /** * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. */ - def appendToVar2IndexMapping(entity: V, defSite: Int): Unit = { + def appendToVar2IndexMapping(entity: SEntity, defSite: Int): Unit = { if (!var2IndexMapping.contains(entity)) { var2IndexMapping(entity) = ListBuffer() } 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 be735c0af4..5a0ec07982 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,7 +134,7 @@ class InterproceduralStringAnalysis( state: InterproceduralComputationState ): StringConstancyProperty = StringConstancyProperty.lb - def analyze(data: P): ProperPropertyComputationResult = { + def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) val state = InterproceduralComputationState(dm, data, fieldWriteThreshold) @@ -371,9 +371,9 @@ class InterproceduralStringAnalysis( getInterimResult(state) } case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - val e = entity.asInstanceOf[P] + val e = entity.asInstanceOf[SContext] // For updating the interim state - state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i => + state.var2IndexMapping(eps.e.asInstanceOf[SContext]._1).foreach { i => state.appendToInterimFpe2Sci(i, p.stringConstancyInformation) } // If necessary, update the parameter information with which the @@ -427,7 +427,7 @@ class InterproceduralStringAnalysis( case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = eps :: state.dependees - val uvar = eps.e.asInstanceOf[P]._1 + val uvar = eps.e.asInstanceOf[SContext]._1 state.var2IndexMapping(uvar).foreach { i => state.appendToInterimFpe2Sci( i, @@ -492,7 +492,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.var2IndexMapping(e.asInstanceOf[P]._1).foreach { + state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { state.appendToFpe2Sci(_, currentSci) } @@ -531,11 +531,11 @@ class InterproceduralStringAnalysis( 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 - 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 Assignment(_, _, fc: FunctionCall[SEntity]) => fc.params + case Assignment(_, _, mc: MethodCall[SEntity]) => mc.params + case ExprStmt(_, fc: FunctionCall[SEntity]) => fc.params + case ExprStmt(_, fc: MethodCall[SEntity]) => fc.params + case mc: MethodCall[SEntity] => mc.params case _ => List() } params.zipWithIndex.foreach { @@ -620,8 +620,8 @@ class InterproceduralStringAnalysis( * [[computeLeanPathForStringBuilder]]. */ private def computeLeanPath( - duvar: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + duvar: SEntity, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): Path = { val defSites = duvar.definedBy.toArray.sorted if (defSites.head < 0) { @@ -641,7 +641,7 @@ class InterproceduralStringAnalysis( * This function computes the lean path for a [[DUVar]] which is required to be a string * expressions. */ - private def computeLeanPathForStringConst(duvar: V): Path = { + private def computeLeanPathForStringConst(duvar: SEntity): Path = { val defSites = duvar.definedBy.toArray.sorted if (defSites.length == 1) { // Trivial case for just one element @@ -665,8 +665,8 @@ class InterproceduralStringAnalysis( * `(null, false)` and otherwise `(computed lean path, true)`. */ private def computeLeanPathForStringBuilder( - duvar: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + duvar: SEntity, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): (Path, Boolean) = { val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) val initDefSites = InterpretationHandler.findDefSiteOfInit(duvar, tac.stmts) @@ -678,14 +678,14 @@ class InterproceduralStringAnalysis( } } - private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { - def hasExprParamUsage(expr: Expr[V]): Boolean = expr match { - case al: ArrayLoad[V] => + private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[SEntity]]): Boolean = { + def hasExprParamUsage(expr: Expr[SEntity]): Boolean = expr match { + case al: ArrayLoad[SEntity] => 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 duvar: SEntity => duvar.definedBy.exists(_ < 0) + case fc: FunctionCall[SEntity] => fc.params.exists(hasExprParamUsage) + case mc: MethodCall[SEntity] => mc.params.exists(hasExprParamUsage) + case be: BinaryExpr[SEntity] => hasExprParamUsage(be.left) || hasExprParamUsage(be.right) case _ => false } @@ -706,12 +706,12 @@ class InterproceduralStringAnalysis( * 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) = { + subpath: SubPath, + stmts: Array[Stmt[SEntity]], + target: SEntity, + foundDependees: ListBuffer[(SEntity, Int)], + hasTargetBeenSeen: Boolean + ): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false subpath match { case fpe: FlatPathElement => @@ -766,11 +766,11 @@ class InterproceduralStringAnalysis( * 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]() + path: Path, + stmts: Array[Stmt[SEntity]], + ignore: SEntity + ): mutable.LinkedHashMap[SEntity, Int] = { + val dependees = mutable.LinkedHashMap[SEntity, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) var wasTargetSeen = false @@ -827,7 +827,7 @@ object InterproceduralStringAnalysis { * 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 = { + def isSupportedPrimitiveNumberType(v: SEntity): Boolean = { val value = v.value if (value.isPrimitiveValue) { isSupportedPrimitiveNumberType(value.asPrimitiveValue.primitiveType.toJava) @@ -858,13 +858,13 @@ object InterproceduralStringAnalysis { typeName == "java.lang.String" || typeName == "java.lang.String[]" /** - * Determines whether a given [[V]] element ([[DUVar]]) is supported by the string analysis. + * Determines whether a given [[SEntity]] 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 = + def isSupportedType(v: SEntity): Boolean = if (v.value.isPrimitiveValue) { isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) } else { 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 bbc871fa06..3503737641 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,17 +73,17 @@ 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[SEntity, 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 = { + def analyze(data: SContext): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation @@ -181,16 +181,16 @@ class IntraproceduralStringAnalysis( * [[FinalP]]. */ private def processFinalP( - data: P, - dependees: Iterable[EOptionP[Entity, Property]], - state: ComputationState, - e: Entity, - p: Property + data: SContext, + 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) + state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[SContext]._1), currentSci) // No more dependees => Return the result for this analysis run val remDependees = dependees.filter(_.e != e) @@ -222,9 +222,9 @@ class IntraproceduralStringAnalysis( * @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 + data: SContext, + 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( @@ -243,12 +243,12 @@ class IntraproceduralStringAnalysis( * [[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) = { + subpath: SubPath, + stmts: Array[Stmt[SEntity]], + target: SEntity, + foundDependees: ListBuffer[(SEntity, Int)], + hasTargetBeenSeen: Boolean + ): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false subpath match { case fpe: FlatPathElement => @@ -303,11 +303,11 @@ class IntraproceduralStringAnalysis( * 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]() + path: Path, + stmts: Array[Stmt[SEntity]], + ignore: SEntity + ): mutable.LinkedHashMap[SEntity, Int] = { + val dependees = mutable.LinkedHashMap[SEntity, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) var wasTargetSeen = 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 7dfc20b301..26b67662cf 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 @@ -38,8 +38,8 @@ import org.opalj.value.ValueInformation * @author Patrick Mell */ abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[V], TACStmts[V]], - protected val exprHandler: InterpretationHandler + protected val cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + protected val exprHandler: InterpretationHandler ) { type T <: Any @@ -59,7 +59,7 @@ abstract class AbstractStringInterpreter( ps: PropertyStore, m: Method, s: InterproceduralComputationState - ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { + ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, SEntity]]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { (tacai, tacai.ub.tac) @@ -102,12 +102,12 @@ abstract class AbstractStringInterpreter( protected def getParametersForPCs( pcs: Iterable[Int], tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): List[Seq[Expr[V]]] = { - val paramLists = ListBuffer[Seq[Expr[V]]]() + ): List[Seq[Expr[SEntity]]] = { + val paramLists = ListBuffer[Seq[Expr[SEntity]]]() 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 ExprStmt(_, vfc: FunctionCall[SEntity]) => vfc.params + case Assignment(_, _, fc: FunctionCall[SEntity]) => fc.params case _ => Seq() } if (params.nonEmpty) { @@ -130,11 +130,11 @@ abstract class AbstractStringInterpreter( * entities to functions, `entity2function`. */ protected def evaluateParameters( - params: List[Seq[Expr[V]]], - iHandler: InterproceduralInterpretationHandler, - funCall: FunctionCall[V], - functionArgsPos: NonFinalFunctionArgsPos, - entity2function: mutable.Map[P, ListBuffer[FunctionCall[V]]] + params: List[Seq[Expr[SEntity]]], + iHandler: InterproceduralInterpretationHandler, + funCall: FunctionCall[SEntity], + functionArgsPos: NonFinalFunctionArgsPos, + entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[SEntity]]] ): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { @@ -146,7 +146,7 @@ abstract class AbstractStringInterpreter( if (!functionArgsPos.contains(funCall)) { functionArgsPos(funCall) = mutable.Map() } - val e = ep.e.asInstanceOf[P] + val e = ep.e.asInstanceOf[SContext] functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) if (!entity2function.contains(e)) { entity2function(e) = 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 d29d6925b8..d3771495d1 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 @@ -81,7 +81,7 @@ object InterpretationHandler { * @return Returns true if `expr` is a call to `toString` of [[StringBuilder]] or * [[StringBuffer]]. */ - def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = + def isStringBuilderBufferToStringCall(expr: Expr[SEntity]): Boolean = expr match { case VirtualFunctionCall(_, clazz, _, "toString", _, _, _) => val className = clazz.mostPreciseObjectType.fqn @@ -96,7 +96,7 @@ object InterpretationHandler { * @return Returns `true` if the given expression is a string constant / literal and `false` * otherwise. */ - def isStringConstExpression(expr: Expr[V]): Boolean = if (expr.isStringConst) { + def isStringConstExpression(expr: Expr[SEntity]): Boolean = if (expr.isStringConst) { true } else { if (expr.isVar) { @@ -112,7 +112,7 @@ object InterpretationHandler { /** * Returns `true` if the given expressions is a primitive number type */ - def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = + def isPrimitiveNumberTypeExpression(expr: Expr[SEntity]): Boolean = expr.asVar.value.isPrimitiveValue && InterproceduralStringAnalysis.isSupportedPrimitiveNumberType( expr.asVar.value.asPrimitiveValue.primitiveType.toJava @@ -126,7 +126,7 @@ object InterpretationHandler { * @return Returns true if `expr` is a call to `append` of [[StringBuilder]] or * [[StringBuffer]]. */ - def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { + def isStringBuilderBufferAppendCall(expr: Expr[SEntity]): Boolean = { expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) => val className = clazz.toJavaClass.getName @@ -140,8 +140,8 @@ object InterpretationHandler { * Helper function for [[findDefSiteOfInit]]. */ private def findDefSiteOfInitAcc( - toString: VirtualFunctionCall[V], - stmts: Array[Stmt[V]] + toString: VirtualFunctionCall[SEntity], + stmts: Array[Stmt[SEntity]] ): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder if (toString.name != "toString") { @@ -154,11 +154,11 @@ object InterpretationHandler { while (stack.nonEmpty) { val next = stack.pop() stmts(next) match { - case a: Assignment[V] => + case a: Assignment[SEntity] => a.expr match { case _: New => defSites.append(next) - case vfc: VirtualFunctionCall[V] => + case vfc: VirtualFunctionCall[SEntity] => 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 @@ -167,7 +167,7 @@ object InterpretationHandler { } else { defSites.append(next) } - case _: GetField[V] => + case _: GetField[SEntity] => defSites.append(next) case _ => // E.g., NullExpr } @@ -187,11 +187,11 @@ object InterpretationHandler { * @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] = { + def findDefSiteOfInit(duvar: SEntity, stmts: Array[Stmt[SEntity]]): List[Int] = { val defSites = ListBuffer[Int]() duvar.definedBy.foreach { ds => defSites.appendAll(stmts(ds).asAssignment.expr match { - case vfc: VirtualFunctionCall[V] => findDefSiteOfInitAcc(vfc, stmts) + case vfc: VirtualFunctionCall[SEntity] => findDefSiteOfInitAcc(vfc, stmts) // The following case is, e.g., for {NonVirtual, Static}FunctionCalls case _ => List(ds) }) @@ -211,19 +211,19 @@ object InterpretationHandler { * @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] = { + def findNewOfVar(duvar: SEntity, stmts: Array[Stmt[SEntity]]): List[New] = { val news = ListBuffer[New]() // HINT: It might be that the search has to be extended to further cases duvar.definedBy.filter(_ >= 0).foreach { ds => stmts(ds) match { // E.g., a call to `toString` or `append` - case Assignment(_, _, vfc: VirtualFunctionCall[V]) => + case Assignment(_, _, vfc: VirtualFunctionCall[SEntity]) => vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs => stmts(innerDs) match { case Assignment(_, _, expr: New) => news.append(expr) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => + case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => val exprReceiverVar = expr.receiver.asVar // The "if" is to avoid endless recursion if (duvar.definedBy != exprReceiverVar.definedBy) { 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 9486ce6c29..3060bc1d3d 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 @@ -27,11 +27,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class BinaryExprInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = BinaryExpr[V] + override type T = BinaryExpr[SEntity] /** * Currently, this implementation supports the interpretation of the following binary 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 1d39dd1e47..a728859776 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 @@ -26,8 +26,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class DoubleValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = DoubleConst 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 4855829f93..321f412015 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 @@ -26,8 +26,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class FloatValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = FloatConst 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 153eccdf4b..d2111da093 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 @@ -26,8 +26,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntegerValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = IntConst 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 fd6e6ff2c3..00081f0d49 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 @@ -23,8 +23,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = New 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 0394605578..3144575c20 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 @@ -27,8 +27,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class StringConstInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StringConst 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 d700990e4f..70345d753d 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 @@ -30,13 +30,13 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class ArrayPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = ArrayLoad[V] + override type T = ArrayLoad[SEntity] /** * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string @@ -105,7 +105,7 @@ class ArrayPreparationInterpreter( object ArrayPreparationInterpreter { - type T = ArrayLoad[V] + type T = ArrayLoad[SEntity] /** * This function retrieves all definition sites of the array stores and array loads that belong @@ -116,7 +116,7 @@ object ArrayPreparationInterpreter { * @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, stmts: Array[Stmt[V]]): List[Int] = { + def getStoreAndLoadDefSites(instr: T, stmts: Array[Stmt[SEntity]]): List[Int] = { val allDefSites = ListBuffer[Int]() val defSites = instr.arrayRef.asVar.definedBy.toArray @@ -125,12 +125,12 @@ object ArrayPreparationInterpreter { val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted // For ArrayStores sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] + stmts(_).isInstanceOf[ArrayStore[SEntity]] } foreach { f: Int => allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) } // For ArrayLoads sortedArrDeclUses.filter { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) => true + case Assignment(_, _, _: ArrayLoad[SEntity]) => true case _ => false } } foreach { f: Int => 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 9196c412c0..f92ec2a7ac 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 @@ -40,7 +40,7 @@ class InterproceduralFieldInterpreter( implicit val contextProvider: ContextProvider ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { - override type T = FieldRead[V] + override type T = FieldRead[SEntity] /** * Currently, fields are approximated using the following approach. If a field of a type not 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 255b015c83..82b97ec448 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 @@ -93,26 +93,26 @@ class InterproceduralInterpretationHandler( 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]) => + case Assignment(_, _, expr: ArrayLoad[SEntity]) => processArrayLoad(expr, defSite, params) - case Assignment(_, _, expr: NewArray[V]) => + case Assignment(_, _, expr: NewArray[SEntity]) => 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) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite, params) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite, params) - case Assignment(_, _, expr: StaticFunctionCall[V]) => + case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => processVFC(expr, defSite, params) + case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => processVFC(expr, defSite, params) + case Assignment(_, _, expr: StaticFunctionCall[SEntity]) => processStaticFunctionCall(expr, defSite, params) - case ExprStmt(_, expr: StaticFunctionCall[V]) => + case ExprStmt(_, expr: StaticFunctionCall[SEntity]) => processStaticFunctionCall(expr, defSite, params) - case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + case Assignment(_, _, expr: BinaryExpr[SEntity]) => processBinaryExpr(expr, defSite) + case Assignment(_, _, expr: NonVirtualFunctionCall[SEntity]) => processNonVirtualFunctionCall(expr, defSite) - case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) - case vmc: VirtualMethodCall[V] => + case Assignment(_, _, expr: GetField[SEntity]) => processGetField(expr, defSite) + case vmc: VirtualMethodCall[SEntity] => processVirtualMethodCall(vmc, defSite, callees) - case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) + case nvmc: NonVirtualMethodCall[SEntity] => processNonVirtualMethodCall(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) @@ -147,9 +147,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[ArrayLoad]]s. */ private def processArrayLoad( - expr: ArrayLoad[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: ArrayLoad[SEntity], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new ArrayPreparationInterpreter( cfg, @@ -171,9 +171,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NewArray]]s. */ private def processNewArray( - expr: NewArray[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: NewArray[SEntity], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new NewArrayPreparer( cfg, @@ -209,9 +209,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ private def processVFC( - expr: VirtualFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: VirtualFunctionCall[SEntity], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( cfg, @@ -257,9 +257,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: StaticFunctionCall[SEntity], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( cfg, @@ -282,8 +282,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[BinaryExpr]]s. */ private def processBinaryExpr( - expr: BinaryExpr[V], - defSite: Int + expr: BinaryExpr[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such // expressions @@ -298,8 +298,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField( - expr: FieldRead[V], - defSite: Int + expr: FieldRead[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( state, @@ -320,8 +320,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualFunctionCall( - expr: NonVirtualFunctionCall[V], - defSite: Int + expr: NonVirtualFunctionCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, @@ -342,9 +342,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[VirtualMethodCall]]s. */ def processVirtualMethodCall( - expr: VirtualMethodCall[V], - defSite: Int, - callees: Callees + expr: VirtualMethodCall[SEntity], + defSite: Int, + callees: Callees ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralVirtualMethodCallInterpreter( cfg, @@ -359,8 +359,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualMethodCall( - nvmc: NonVirtualMethodCall[V], - defSite: Int + nvmc: NonVirtualMethodCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( cfg, @@ -425,23 +425,23 @@ class InterproceduralInterpretationHandler( state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) } else { stmts(defSite) match { - case nvmc: NonVirtualMethodCall[V] => + case nvmc: NonVirtualMethodCall[SEntity] => NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) - case Assignment(_, _, al: ArrayLoad[V]) => + case Assignment(_, _, al: ArrayLoad[SEntity]) => ArrayLoadFinalizer(state, cfg).finalizeInterpretation(al, defSite) - case Assignment(_, _, na: NewArray[V]) => + case Assignment(_, _, na: NewArray[SEntity]) => NewArrayFinalizer(state, cfg).finalizeInterpretation(na, defSite) - case Assignment(_, _, vfc: VirtualFunctionCall[V]) => + case Assignment(_, _, vfc: VirtualFunctionCall[SEntity]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case ExprStmt(_, vfc: VirtualFunctionCall[V]) => + case ExprStmt(_, vfc: VirtualFunctionCall[SEntity]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case Assignment(_, _, fr: FieldRead[V]) => + case Assignment(_, _, fr: FieldRead[SEntity]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case ExprStmt(_, fr: FieldRead[V]) => + case ExprStmt(_, fr: FieldRead[SEntity]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case Assignment(_, _, sfc: StaticFunctionCall[V]) => + case Assignment(_, _, sfc: StaticFunctionCall[SEntity]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) - case ExprStmt(_, sfc: StaticFunctionCall[V]) => + case ExprStmt(_, sfc: StaticFunctionCall[SEntity]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case _ => state.appendToFpe2Sci( 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 a981635d52..e2b59837a5 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 @@ -27,15 +27,15 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualFunctionCall[V] + override type T = NonVirtualFunctionCall[SEntity] /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns @@ -60,14 +60,14 @@ class InterproceduralNonVirtualFunctionCallInterpreter( if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVars and start the string analysis - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, // is approximated with the lower bound FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val uvar = ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar val entity = (uvar, m) val eps = ps(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 7c0210d876..2e70bd7bfe 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,14 +26,14 @@ import org.opalj.fpcf.PropertyStore * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualMethodCall[V] + override type T = NonVirtualMethodCall[SEntity] /** * Currently, this function supports the interpretation of the following non virtual methods: @@ -51,8 +51,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[V], - defSite: Int + instr: NonVirtualMethodCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { @@ -69,8 +69,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( * these are currently interpreted). */ private def interpretInit( - init: NonVirtualMethodCall[V], - defSite: Integer + init: NonVirtualMethodCall[SEntity], + defSite: Integer ): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { case 0 => FinalEP(defSite, 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 175b29ac96..1cff691b6c 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 @@ -32,16 +32,16 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]], + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = StaticFunctionCall[V] + override type T = StaticFunctionCall[SEntity] /** * This function always returns a list with a single element consisting of @@ -70,7 +70,7 @@ class InterproceduralStaticFunctionCallInterpreter( * the parameter passed to the call. */ private def processStringValueOf( - call: StaticFunctionCall[V] + call: StaticFunctionCall[SEntity] ): EOptionP[Entity, StringConstancyProperty] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds => exprHandler.processDefSite(ds, params) @@ -101,8 +101,8 @@ class InterproceduralStaticFunctionCallInterpreter( * This function interprets an arbitrary static function call. */ private def processArbitraryCall( - instr: StaticFunctionCall[V], - defSite: Int + instr: StaticFunctionCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC( instr.pc, @@ -147,9 +147,9 @@ class InterproceduralStaticFunctionCallInterpreter( val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (nonFinalResults.nonEmpty) { if (tac.isDefined) { - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) returns.foreach { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) + val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, m) val eps = ps(entity, StringConstancyProperty.key) state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) @@ -166,14 +166,14 @@ class InterproceduralStaticFunctionCallInterpreter( if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, // is approximated with the lower bound FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) + val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, m) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(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 17a19a8c22..b82832b8a9 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 @@ -27,12 +27,12 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class InterproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualMethodCall[V] + override type T = VirtualMethodCall[SEntity] /** * Currently, this function supports the interpretation of the following virtual methods: 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 48e996d44a..b10e3bd2a1 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 @@ -27,13 +27,13 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewArrayPreparer( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NewArray[V] + override type T = NewArray[SEntity] /** * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string @@ -55,7 +55,7 @@ class NewArrayPreparer( 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]] + ds => ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[SEntity]] }.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 => 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 d814fc4774..cfeaccd8ba 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 @@ -35,16 +35,16 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class VirtualFunctionCallPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]], - typeIterator: TypeIterator + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + params: List[Seq[StringConstancyInformation]], + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualFunctionCall[V] + override type T = VirtualFunctionCall[SEntity] /** * Currently, this implementation supports the interpretation of the following function calls: @@ -158,18 +158,18 @@ class VirtualFunctionCallPreparationInterpreter( val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { state.methodPrep2defSite.remove(nextMethod) - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) if (returns.isEmpty) { // It might be that a function has no return value, e. g., in case it is // guaranteed to throw an exception FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, nextMethod) + val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, nextMethod) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { - case r: FinalEP[P, StringConstancyProperty] => + case r: FinalEP[SContext, StringConstancyProperty] => state.appendToFpe2Sci(defSite, r.p.stringConstancyInformation) r case _ => @@ -201,8 +201,8 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[V], - defSite: Int + appendCall: VirtualFunctionCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) val appendResult = valueOfAppendCall(appendCall, state) @@ -264,8 +264,8 @@ class VirtualFunctionCallPreparationInterpreter( * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V], - state: InterproceduralComputationState + call: VirtualFunctionCall[SEntity], + state: InterproceduralComputationState ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -298,8 +298,8 @@ class VirtualFunctionCallPreparationInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], - state: InterproceduralComputationState + call: VirtualFunctionCall[SEntity], + state: InterproceduralComputationState ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar @@ -378,7 +378,7 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretToStringCall( - call: VirtualFunctionCall[V] + call: VirtualFunctionCall[SEntity] ): EOptionP[Entity, StringConstancyProperty] = // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) @@ -389,7 +389,7 @@ class VirtualFunctionCallPreparationInterpreter( * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( - instr: VirtualFunctionCall[V] + instr: VirtualFunctionCall[SEntity] ): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index a4e2b9260d..114979a313 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -18,10 +18,10 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation */ class ArrayLoadFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ) extends AbstractFinalizer(state) { - override type T = ArrayLoad[V] + override type T = ArrayLoad[SEntity] /** * Finalizes [[ArrayLoad]]s. @@ -51,7 +51,7 @@ object ArrayLoadFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ): ArrayLoadFinalizer = new ArrayLoadFinalizer(state, cfg) } 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 286b0a277c..9d86275a0d 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 @@ -12,7 +12,7 @@ class GetFieldFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override protected type T = FieldRead[V] + override protected type T = FieldRead[SEntity] /** * Finalizes [[FieldRead]]s. 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 index 1f618fe2e1..f88a5b82e4 100644 --- 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 @@ -15,10 +15,10 @@ import org.opalj.br.cfg.CFG */ class NewArrayFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ) extends AbstractFinalizer(state) { - override type T = NewArray[V] + override type T = NewArray[SEntity] /** * Finalizes [[NewArray]]s. @@ -35,7 +35,7 @@ object NewArrayFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ): NewArrayFinalizer = new NewArrayFinalizer(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 a37d789916..31356a0a86 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 @@ -18,7 +18,7 @@ class NonVirtualMethodCallFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override type T = NonVirtualMethodCall[V] + override type T = NonVirtualMethodCall[SEntity] /** * Finalizes [[NonVirtualMethodCall]]s. 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 index 5ea0b2cfcc..0919f59836 100644 --- 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 @@ -18,7 +18,7 @@ class StaticFunctionCallFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override type T = StaticFunctionCall[V] + override type T = StaticFunctionCall[SEntity] /** * Finalizes [[StaticFunctionCall]]s. 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 09c4fa0e8d..eecb8ef28f 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 @@ -19,10 +19,10 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType */ class VirtualFunctionCallFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ) extends AbstractFinalizer(state) { - override type T = VirtualFunctionCall[V] + override type T = VirtualFunctionCall[SEntity] /** * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" and @@ -121,7 +121,7 @@ object VirtualFunctionCallFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) } 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 34337d0c7f..254f188546 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 @@ -25,11 +25,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralArrayInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = ArrayLoad[V] + override type T = ArrayLoad[SEntity] /** * @note For this implementation, `defSite` does not play a role. @@ -46,7 +46,7 @@ class IntraproceduralArrayInterpreter( val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted // Process ArrayStores sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] + stmts(_).isInstanceOf[ArrayStore[SEntity]] } foreach { f: Int => val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted children.appendAll(sortedDefs.map { exprHandler.processDefSite(_) }.map { n => @@ -56,7 +56,7 @@ class IntraproceduralArrayInterpreter( // Process ArrayLoads sortedArrDeclUses.filter { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) => true + case Assignment(_, _, _: ArrayLoad[SEntity]) => true case _ => false } } foreach { f: Int => 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 65163a5d90..6d0d8271b5 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 @@ -24,11 +24,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralFieldInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = GetField[V] + override type T = GetField[SEntity] /** * Fields are not suppoerted by this implementation. Thus, this function always returns a result 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 e4fe9503e1..96c791ddec 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 @@ -24,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralGetStaticInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + 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/intraprocedural/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 0a43050ab0..e946a463a4 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,36 +65,36 @@ class IntraproceduralInterpretationHandler( new FloatValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: DoubleConst) => new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: ArrayLoad[V]) => + case Assignment(_, _, expr: ArrayLoad[SEntity]) => new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: New) => new NewInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => + case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: StaticFunctionCall[V]) => + case Assignment(_, _, expr: StaticFunctionCall[SEntity]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) => + case Assignment(_, _, expr: BinaryExpr[SEntity]) => new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + case Assignment(_, _, expr: NonVirtualFunctionCall[SEntity]) => new IntraproceduralNonVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: GetField[V]) => + case Assignment(_, _, expr: GetField[SEntity]) => new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => + case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) => + case ExprStmt(_, expr: StaticFunctionCall[SEntity]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case vmc: VirtualMethodCall[V] => + case vmc: VirtualMethodCall[SEntity] => new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) - case nvmc: NonVirtualMethodCall[V] => + case nvmc: NonVirtualMethodCall[SEntity] => new IntraproceduralNonVirtualMethodCallInterpreter( cfg, this 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 737191e5f8..4969a59e09 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 @@ -22,11 +22,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualFunctionCall[V] + override type T = NonVirtualFunctionCall[SEntity] /** * This function always returns a result that contains [[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 8cae654391..69d0d8a0a9 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 @@ -26,11 +26,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualMethodCall[V] + override type T = NonVirtualMethodCall[SEntity] /** * Currently, this function supports the interpretation of the following non virtual methods: @@ -50,8 +50,8 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[V], - defSite: Int + instr: NonVirtualMethodCall[SEntity], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { case "" => interpretInit(instr) @@ -67,7 +67,7 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * [[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[SEntity]): StringConstancyProperty = { init.params.size match { case 0 => StringConstancyProperty.getNeutralElement case _ => 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 b57243f9d7..5bc43d01f4 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 @@ -24,11 +24,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = StaticFunctionCall[V] + override type T = StaticFunctionCall[SEntity] /** * This function always returns a result containing [[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 025a251032..c45f51ad0c 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 @@ -32,11 +32,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualFunctionCall[V] + override type T = VirtualFunctionCall[SEntity] /** * Currently, this implementation supports the interpretation of the following function calls: @@ -93,7 +93,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[V] + appendCall: VirtualFunctionCall[SEntity] ): StringConstancyProperty = { val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation @@ -130,7 +130,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * This function determines the current value of the receiver object of an `append` call. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V] + call: VirtualFunctionCall[SEntity] ): 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 @@ -149,7 +149,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V] + call: VirtualFunctionCall[SEntity] ): StringConstancyProperty = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append @@ -200,7 +200,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretToStringCall( - call: VirtualFunctionCall[V] + call: VirtualFunctionCall[SEntity] ): StringConstancyProperty = { val finalEP = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asFinal finalEP.p.asInstanceOf[StringConstancyProperty] @@ -212,7 +212,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( - instr: VirtualFunctionCall[V] + instr: VirtualFunctionCall[SEntity] ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace } 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 9a7c994a8c..58152aca1d 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 @@ -26,11 +26,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualMethodCall[V] + override type T = VirtualMethodCall[SEntity] /** * Currently, this function supports the interpretation of the following virtual methods: 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 45c6aeffb2..d3f4709a62 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 @@ -22,7 +22,7 @@ import org.opalj.br.cfg.CFGNode * * @author Patrick Mell */ -abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { +abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { /** * CSInfo stores information regarding control structures (CS) in the form: Index of the start @@ -79,7 +79,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { }.max var containsIf = false for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { processedIfs(i) = () containsIf = true } @@ -134,11 +134,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } case _ => // No goto available => Jump after next block - var nextIf: Option[If[V]] = None + var nextIf: Option[If[SEntity]] = None var i = nextBlock while (i < cfg.code.instructions.length && nextIf.isEmpty) { cfg.code.instructions(i) match { - case iff: If[V] => + case iff: If[SEntity] => nextIf = Some(iff) processedIfs(i) = () case _ => @@ -189,11 +189,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { }.max var nextIfIndex = -1 - val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt + val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[SEntity]].targetStmt for (i <- cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { // 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) { + if (cfg.code.instructions(i).isInstanceOf[If[SEntity]] && ifTarget != i) { nextIfIndex = i } } @@ -246,7 +246,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { cfg.bb(ifTarget).predecessors.foreach { case pred: BasicBlock => for (i <- pred.startPC.to(pred.endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { processedIfs(i) = () } } @@ -268,7 +268,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var returnPos = startPos var foundReturn = false while (!foundReturn && returnPos < cfg.code.instructions.length) { - if (cfg.code.instructions(returnPos).isInstanceOf[ReturnValue[V]]) { + if (cfg.code.instructions(returnPos).isInstanceOf[ReturnValue[SEntity]]) { foundReturn = true } else { returnPos += 1 @@ -451,7 +451,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { nextBlock = popped + 1 while (nextBlock < cfg.code.instructions.length - 1 && - !cfg.code.instructions(nextBlock).isInstanceOf[If[V]] + !cfg.code.instructions(nextBlock).isInstanceOf[If[SEntity]] ) { nextBlock += 1 } @@ -459,7 +459,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { var containsIf = false for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { containsIf = true } } @@ -643,11 +643,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 iff: Option[If[SEntity]] = None var i = indexFirstAfterCatch while (iff.isEmpty) { cfg.code.instructions(i) match { - case foundIf: If[V] => iff = Some(foundIf) + case foundIf: If[SEntity] => iff = Some(foundIf) case _ => } i += 1 @@ -708,7 +708,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { protected def isHeadOfLoop( site: Int, loops: List[List[Int]], - cfg: CFG[Stmt[V], TACStmts[V]] + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] ): Boolean = { var belongsToLoopHeader = false @@ -730,7 +730,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (!belongsToLoopHeader) { val start = nextLoop.head var end = start - while (!cfg.code.instructions(end).isInstanceOf[If[V]]) { + while (!cfg.code.instructions(end).isInstanceOf[If[SEntity]]) { end += 1 } if (site >= start && site <= end && end < nextLoop.last) { @@ -780,9 +780,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''else'' branch. */ protected def isCondWithoutElse( - branchingSite: Int, - cfg: CFG[Stmt[V], TACStmts[V]], - processedIfs: mutable.Map[Int, Unit] + branchingSite: Int, + cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + processedIfs: mutable.Map[Int, Unit] ): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative @@ -813,7 +813,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val indexIf = cfg.bb(lastEle) match { case bb: BasicBlock => val ifPos = bb.startPC.to(bb.endPC).filter( - cfg.code.instructions(_).isInstanceOf[If[V]] + cfg.code.instructions(_).isInstanceOf[If[SEntity]] ) if (ifPos.nonEmpty && !isHeadOfLoop(ifPos.head, cfg.findNaturalLoops(), cfg)) { ifPos.head @@ -1057,10 +1057,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case bb: BasicBlock => for (i <- bb.startPC.to(bb.endPC)) { cfg.code.instructions(i) match { - case _: If[V] if !processedIfs.contains(i) => + case _: If[SEntity] if !processedIfs.contains(i) => foundCS.append(processIf(i, processedIfs)) processedIfs(i) = () - case _: Switch[V] if !processedSwitches.contains(i) => + case _: Switch[SEntity] if !processedSwitches.contains(i) => foundCS.append(processSwitch(i)) processedSwitches(i) = () case _ => @@ -1126,7 +1126,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val element = toTransform.hierarchy.head._1.get val start = element._1 val end = element._2 - if (cfg.code.instructions(start).isInstanceOf[Switch[V]]) { + if (cfg.code.instructions(start).isInstanceOf[Switch[SEntity]]) { buildPathForSwitch(start, end, fill) } else { element._3 match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index d2a0b866b5..9cdfa97cf2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -23,7 +23,7 @@ import org.opalj.br.cfg.CFG * jumps within the bytecode might lead to a different order than the one computed by this * class! */ -class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { +class DefaultPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) extends AbstractPathFinder(cfg) { /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` 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 fbbf9fb5a7..1e0e3658e8 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 @@ -83,7 +83,7 @@ case class Path(elements: List[SubPath]) { */ private def getAllDefAndUseSites( obj: DUVar[ValueInformation], - stmts: Array[Stmt[V]] + stmts: Array[Stmt[SEntity]] ): List[Int] = { val defAndUses = ListBuffer[Int]() val stack = mutable.Stack[Int](obj.definedBy.toList: _*) @@ -94,12 +94,12 @@ case class Path(elements: List[SubPath]) { defAndUses.append(popped) stmts(popped) match { - case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] => + case a: Assignment[SEntity] if a.expr.isInstanceOf[VirtualFunctionCall[SEntity]] => 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] => + case a: Assignment[SEntity] if a.expr.isInstanceOf[New] => stack.pushAll(a.targetVar.usedBy.toArray) case _ => } @@ -266,15 +266,15 @@ case class Path(elements: List[SubPath]) { * 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]]): Path = { + def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[SEntity]]): Path = { 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]) => + case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => + case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) case _ => true diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index c7e89498db..9c7dd30ea4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -22,7 +22,7 @@ import org.opalj.br.cfg.CFG * 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) { +class WindowPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) extends AbstractPathFinder(cfg) { /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` @@ -42,9 +42,9 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde var nextStmt = startSites.min while (nextStmt >= 0 && startSite.isEmpty) { cfg.code.instructions(nextStmt) match { - case iff: If[V] if startSites.contains(iff.targetStmt) => + case iff: If[SEntity] if startSites.contains(iff.targetStmt) => startSite = Some(nextStmt) - case _: Switch[V] => + case _: Switch[SEntity] => val (startSwitch, endSwitch, _) = processSwitch(nextStmt) val isParentSwitch = startSites.forall { nextStartSite => nextStartSite >= startSwitch && nextStartSite <= endSwitch 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 70c9c7b80d..d12b519a47 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 @@ -19,17 +19,17 @@ import org.opalj.value.ValueInformation package object string_analysis { /** - * The type of entities the [[IntraproceduralStringAnalysis]] processes. + * The type of entities the [[IntraproceduralStringAnalysis]] and the [[InterproceduralStringAnalysis]] process. * - * @note The analysis requires further context information, see [[P]]. + * @note The analysis require further context information, see [[SContext]]. */ - type V = DUVar[ValueInformation] + type SEntity = DUVar[ValueInformation] /** - * [[IntraproceduralStringAnalysis]] processes a local variable within the context of a - * particular context, i.e., the method in which it is used. + * [[IntraproceduralStringAnalysis]] and [[InterproceduralStringAnalysis]] process a local variable within a + * particular context, i.e. the method in which it is used. TODO should this be "context"? */ - type P = (V, Method) + type SContext = (SEntity, Method) /** * This type indicates how (non-final) parameters of functions are represented. The outer-most @@ -42,11 +42,11 @@ package object string_analysis { /** * 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 + * argument position. That is, the element of type [[SContext]] 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)]] + type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[SEntity], mutable.Map[SContext, (Int, Int, Int)]] } From b479aac19c9b0586d2af52c30fc3a4476c23650a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 25 Jan 2024 20:54:22 +0100 Subject: [PATCH 333/583] Swap out processed entity and context --- .../info/StringAnalysisReflectiveCalls.scala | 35 +++--- .../org/opalj/fpcf/StringAnalysisTest.scala | 21 ++-- .../InterproceduralComputationState.scala | 6 +- .../InterproceduralStringAnalysis.scala | 113 ++++++++---------- .../IntraproceduralStringAnalysis.scala | 64 +++++----- .../AbstractStringInterpreter.scala | 18 +-- .../InterpretationHandler.scala | 36 +++--- .../common/BinaryExprInterpreter.scala | 4 +- .../common/DoubleValueInterpreter.scala | 2 +- .../common/FloatValueInterpreter.scala | 2 +- .../common/IntegerValueInterpreter.scala | 2 +- .../common/NewInterpreter.scala | 2 +- .../common/StringConstInterpreter.scala | 2 +- .../ArrayPreparationInterpreter.scala | 20 ++-- .../InterproceduralFieldInterpreter.scala | 5 +- ...InterproceduralInterpretationHandler.scala | 60 +++++----- ...ralNonVirtualFunctionCallInterpreter.scala | 12 +- ...duralNonVirtualMethodCallInterpreter.scala | 14 +-- ...ceduralStaticFunctionCallInterpreter.scala | 30 ++--- ...oceduralVirtualMethodCallInterpreter.scala | 4 +- .../interprocedural/NewArrayPreparer.scala | 8 +- ...alFunctionCallPreparationInterpreter.scala | 18 +-- .../finalizer/ArrayLoadFinalizer.scala | 6 +- .../finalizer/GetFieldFinalizer.scala | 2 +- .../finalizer/NewArrayFinalizer.scala | 6 +- .../NonVirtualMethodCallFinalizer.scala | 2 +- .../StaticFunctionCallFinalizer.scala | 2 +- .../VirtualFunctionCallFinalizer.scala | 6 +- .../IntraproceduralArrayInterpreter.scala | 8 +- .../IntraproceduralFieldInterpreter.scala | 4 +- .../IntraproceduralGetStaticInterpreter.scala | 2 +- ...IntraproceduralInterpretationHandler.scala | 20 ++-- ...ralNonVirtualFunctionCallInterpreter.scala | 4 +- ...duralNonVirtualMethodCallInterpreter.scala | 8 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...eduralVirtualFunctionCallInterpreter.scala | 14 +-- ...oceduralVirtualMethodCallInterpreter.scala | 4 +- .../preprocessing/AbstractPathFinder.scala | 38 +++--- .../preprocessing/DefaultPathFinder.scala | 2 +- .../string_analysis/preprocessing/Path.scala | 12 +- .../preprocessing/WindowPathFinder.scala | 6 +- .../string_analysis/string_analysis.scala | 7 +- .../main/scala/org/opalj/tac/package.scala | 1 + 43 files changed, 310 insertions(+), 326 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 6b3a5b8fe7..f4a2539071 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 @@ -4,11 +4,9 @@ package support package info import scala.annotation.switch - import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.Method import org.opalj.br.ReferenceType import org.opalj.br.analyses.BasicReport @@ -35,12 +33,14 @@ import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.V import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.SContext -import org.opalj.tac.fpcf.analyses.string_analysis.SEntity import org.opalj.tac.fpcf.properties.TACAI /** @@ -180,9 +180,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { private def processFunctionCall( ps: PropertyStore, method: Method, - call: Call[SEntity], + call: Call[V], resultMap: ResultMapType - ): Unit = { + )(implicit stmts: Array[Stmt[V]]): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) if (!ignoreMethods.contains(fqnMethodName)) { @@ -194,8 +194,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { call.descriptor.parameterTypes.zipWithIndex.foreach { case (ft, index) => if (ft.toJava == "java.lang.String") { - val duvar = call.params(index).asVar - val e = (duvar, method) + val e = (call.params(index).asVar.toPersistentForm, method) ps.force(e, StringConstancyProperty.key) entityContext.append( (e, buildFQMethodName(call.declaringClass, call.name)) @@ -233,25 +232,26 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } private def processStatements( - ps: PropertyStore, - stmts: Array[Stmt[SEntity]], - m: Method, - resultMap: ResultMapType + ps: PropertyStore, + tac: TACode[TACMethodParameter, V], + m: Method, + resultMap: ResultMapType ): Unit = { + implicit val stmts: Array[Stmt[V]] = tac.stmts 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[SEntity]) => + case Assignment(_, _, c: StaticFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case Assignment(_, _, c: VirtualFunctionCall[SEntity]) => + case Assignment(_, _, c: VirtualFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) case _ => } case ExprStmt.ASTID => stmt match { - case ExprStmt(_, c: StaticFunctionCall[SEntity]) => + case ExprStmt(_, c: StaticFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) - case ExprStmt(_, c: VirtualFunctionCall[SEntity]) => + case ExprStmt(_, c: VirtualFunctionCall[V]) => processFunctionCall(ps, m, c, resultMap) case _ => } @@ -267,7 +267,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) => - processStatements(ps, tac.tac.get.stmts, m, resultMap) + processStatements(ps, tac.tac.get, m, resultMap) Result(m, tac) case InterimLUBP(lb, ub) => InterimResult( @@ -324,8 +324,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { // No TAC available, e.g., because the method has no body println(s"No body for method: ${m.classFile.fqn}#${m.name}") } else { - val tac = tacaiEOptP.ub.tac.get - processStatements(propertyStore, tac.stmts, m, resultMap) + processStatements(propertyStore, tacaiEOptP.ub.tac.get, m, resultMap) } } else { InterimResult( 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 7b4b748233..5a06472939 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,19 +5,18 @@ package fpcf import java.io.File import java.net.URL import scala.collection.mutable.ListBuffer - import org.opalj.br.Annotation import org.opalj.br.Annotations import org.opalj.br.Method import org.opalj.br.analyses.Project -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.FPCFAnalysesManagerKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis @@ -73,10 +72,10 @@ sealed class StringAnalysisTestRunner( ) } foreach { m => StringAnalysisTestRunner.extractUVars( - tacProvider(m).cfg, + tacProvider(m), fqTestMethodsClass, nameTestMethod - ).foreach { uvar => entitiesToAnalyze.append((uvar, m)) } + ).foreach { puVar => entitiesToAnalyze.append((puVar, m)) } } entitiesToAnalyze } @@ -128,15 +127,15 @@ object StringAnalysisTestRunner { * order in which they occurred in the given statements. */ def extractUVars( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], - fqTestMethodsClass: String, - nameTestMethod: String + tac: TACode[TACMethodParameter, V], + fqTestMethodsClass: String, + nameTestMethod: String ): List[SEntity] = { - cfg.code.instructions.filter { + tac.cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) => declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod case _ => false - }.map(_.asVirtualMethodCall.params.head.asVar).toList + }.map(_.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)).toList } } 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 c8e13afd2b..c95837a91a 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 @@ -132,13 +132,13 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, * This map is used to actually store the interpretations of parameters passed to functions. * For further information, see [[NonFinalFunctionArgs]]. */ - val nonFinalFunctionArgs: mutable.Map[FunctionCall[SEntity], NonFinalFunctionArgs] = mutable.Map() + 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[SContext, ListBuffer[FunctionCall[SEntity]]] = mutable.Map() + val entity2Function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] = mutable.Map() /** * A mapping from a method to definition sites which indicates that a method is still prepared, @@ -150,7 +150,7 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, /** * A mapping which indicates whether a virtual function call is fully prepared. */ - val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[SEntity], Boolean] = mutable.Map() + val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[V], Boolean] = mutable.Map() /** * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] 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 5a0ec07982..6a6fb64061 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 @@ -165,11 +165,10 @@ class InterproceduralStringAnalysis( * 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( - state: InterproceduralComputationState - ): ProperPropertyComputationResult = { - val uvar = state.entity._1 - val defSites = uvar.definedBy.toArray.sorted + private def determinePossibleStrings(state: InterproceduralComputationState): ProperPropertyComputationResult = { + val puVar = state.entity._1 + val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) + val defSites = uVar.definedBy.toArray.sorted if (state.tac == null || state.callees == null) { return getInterimResult(state) @@ -178,7 +177,7 @@ class InterproceduralStringAnalysis( val stmts = state.tac.stmts if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uvar, state.tac) + state.computedLeanPath = computeLeanPath(uVar, state.tac) } if (state.iHandler == null) { @@ -217,10 +216,10 @@ class InterproceduralStringAnalysis( // (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)) { + if (InterpretationHandler.isStringConstExpression(uVar)) { hasCallersOrParamInfo - } else if (InterproceduralStringAnalysis.isSupportedPrimitiveNumberType(uvar)) { - val numType = uvar.value.asPrimitiveValue.primitiveType.toJava + } else if (InterproceduralStringAnalysis.isSupportedPrimitiveNumberType(uVar)) { + val numType = uVar.value.asPrimitiveValue.primitiveType.toJava val sci = InterproceduralStringAnalysis.getDynamicStringInformationForNumberType(numType) return Result(state.entity, StringConstancyProperty(sci)) } else { @@ -230,7 +229,7 @@ class InterproceduralStringAnalysis( } else { val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (_, hasInitDefSites) = computeLeanPathForStringBuilder(uvar, state.tac) + val (_, hasInitDefSites) = computeLeanPathForStringBuilder(uVar, state.tac) if (!hasInitDefSites) { return Result(state.entity, StringConstancyProperty.lb) } @@ -273,23 +272,21 @@ 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.map(_.toList)) - val sci = r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - return Result(state.entity, StringConstancyProperty(sci)) + return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) } 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) + val dependentVars = findDependentVars(state.computedLeanPath, stmts, puVar)(state.tac) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar => - val toAnalyze = (nextVar, state.entity._2) dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } - val ep = propertyStore(toAnalyze, StringConstancyProperty.key) + val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) ep match { - case FinalP(p) => return processFinalP(state, ep.e, p) - case _ => state.dependees = ep :: state.dependees + case FinalEP(e, p) => return processFinalP(state, e, p) + case _ => state.dependees = ep :: state.dependees } } } else { @@ -531,11 +528,11 @@ class InterproceduralStringAnalysis( 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[SEntity]) => fc.params - case Assignment(_, _, mc: MethodCall[SEntity]) => mc.params - case ExprStmt(_, fc: FunctionCall[SEntity]) => fc.params - case ExprStmt(_, fc: MethodCall[SEntity]) => fc.params - case mc: MethodCall[SEntity] => mc.params + 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.zipWithIndex.foreach { @@ -549,7 +546,7 @@ class InterproceduralStringAnalysis( } // Recursively analyze supported types if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { - val paramEntity = (p.asVar, m.definedMethod) + val paramEntity = (p.asVar.toPersistentForm(state.tac.stmts), m.definedMethod) val eps = propertyStore(paramEntity, StringConstancyProperty.key) state.appendToVar2IndexMapping(paramEntity._1, paramIndex) eps match { @@ -595,8 +592,7 @@ class InterproceduralStringAnalysis( if (!state.fpe2sci.contains(index)) { val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq)) if (eOptP.isFinal) { - val p = eOptP.asFinal.p.asInstanceOf[StringConstancyProperty] - state.appendToFpe2Sci(index, p.stringConstancyInformation, reset = true) + state.appendToFpe2Sci(index, eOptP.asFinal.p.stringConstancyInformation, reset = true) } else { hasFinalResult = false } @@ -620,19 +616,19 @@ class InterproceduralStringAnalysis( * [[computeLeanPathForStringBuilder]]. */ private def computeLeanPath( - duvar: SEntity, + value: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): Path = { - val defSites = duvar.definedBy.toArray.sorted + val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { - computeLeanPathForStringConst(duvar) + computeLeanPathForStringConst(value) } else { val call = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (leanPath, _) = computeLeanPathForStringBuilder(duvar, tac) + val (leanPath, _) = computeLeanPathForStringBuilder(value, tac) leanPath } else { - computeLeanPathForStringConst(duvar) + computeLeanPathForStringConst(value) } } } @@ -641,8 +637,8 @@ class InterproceduralStringAnalysis( * This function computes the lean path for a [[DUVar]] which is required to be a string * expressions. */ - private def computeLeanPathForStringConst(duvar: SEntity): Path = { - val defSites = duvar.definedBy.toArray.sorted + private def computeLeanPathForStringConst(value: V): Path = { + val defSites = value.definedBy.toArray.sorted if (defSites.length == 1) { // Trivial case for just one element Path(List(FlatPathElement(defSites.head))) @@ -665,27 +661,26 @@ class InterproceduralStringAnalysis( * `(null, false)` and otherwise `(computed lean path, true)`. */ private def computeLeanPathForStringBuilder( - duvar: SEntity, + value: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): (Path, Boolean) = { val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) - val initDefSites = InterpretationHandler.findDefSiteOfInit(duvar, tac.stmts) + val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) if (initDefSites.isEmpty) { (null, false) } else { - val paths = pathFinder.findPaths(initDefSites, duvar.definedBy.toArray.max) - (paths.makeLeanPath(duvar, tac.stmts), true) + val paths = pathFinder.findPaths(initDefSites, value.definedBy.toArray.max) + (paths.makeLeanPath(value, tac.stmts), true) } } - private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[SEntity]]): Boolean = { - def hasExprParamUsage(expr: Expr[SEntity]): Boolean = expr match { - case al: ArrayLoad[SEntity] => - ArrayPreparationInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) - case duvar: SEntity => duvar.definedBy.exists(_ < 0) - case fc: FunctionCall[SEntity] => fc.params.exists(hasExprParamUsage) - case mc: MethodCall[SEntity] => mc.params.exists(hasExprParamUsage) - case be: BinaryExpr[SEntity] => hasExprParamUsage(be.left) || hasExprParamUsage(be.right) + 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 } @@ -707,15 +702,15 @@ class InterproceduralStringAnalysis( */ private def findDependeesAcc( subpath: SubPath, - stmts: Array[Stmt[SEntity]], + stmts: Array[Stmt[V]], target: SEntity, foundDependees: ListBuffer[(SEntity, Int)], hasTargetBeenSeen: Boolean - ): (ListBuffer[(SEntity, Int)], Boolean) = { + )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false subpath match { case fpe: FlatPathElement => - if (target.definedBy.contains(fpe.element)) { + if (target.toValueOriginForm(tac.pcToIndex).definedBy.contains(fpe.element)) { encounteredTarget = true } // For FlatPathElements, search for DUVars on which the toString method is called @@ -728,7 +723,7 @@ class InterproceduralStringAnalysis( val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append(( - outerExpr.asVirtualFunctionCall.params.head.asVar, + outerExpr.asVirtualFunctionCall.params.head.asVar.toPersistentForm(tac.stmts), fpe.element )) } @@ -767,11 +762,11 @@ class InterproceduralStringAnalysis( */ private def findDependentVars( path: Path, - stmts: Array[Stmt[SEntity]], + stmts: Array[Stmt[V]], ignore: SEntity - ): mutable.LinkedHashMap[SEntity, Int] = { + )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() - val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) + val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), stmts) var wasTargetSeen = false path.elements.foreach { nextSubpath => @@ -785,7 +780,7 @@ class InterproceduralStringAnalysis( ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => - val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1.toValueOriginForm(tac.pcToIndex), stmts) if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) } @@ -827,14 +822,8 @@ object InterproceduralStringAnalysis { * This function checks whether a given type is a supported primitive type. Supported currently * means short, int, float, or double. */ - def isSupportedPrimitiveNumberType(v: SEntity): Boolean = { - val value = v.value - if (value.isPrimitiveValue) { - isSupportedPrimitiveNumberType(value.asPrimitiveValue.primitiveType.toJava) - } else { - false - } - } + def isSupportedPrimitiveNumberType(v: V): Boolean = + v.value.isPrimitiveValue && isSupportedPrimitiveNumberType(v.value.asPrimitiveValue.primitiveType.toJava) /** * This function checks whether a given type is a supported primitive type. Supported currently @@ -858,13 +847,13 @@ object InterproceduralStringAnalysis { typeName == "java.lang.String" || typeName == "java.lang.String[]" /** - * Determines whether a given [[SEntity]] element ([[DUVar]]) is supported by the string analysis. + * Determines whether a given element 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: SEntity): Boolean = + def isSupportedType(v: V): Boolean = if (v.value.isPrimitiveValue) { isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) } else { 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 3503737641..bd670a14aa 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[SEntity, 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[SEntity, 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: SContext): ProperPropertyComputationResult = { @@ -93,7 +93,7 @@ class IntraproceduralStringAnalysis( if (tacaiEOptP.hasUBP) { if (tacaiEOptP.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - return Result(data, StringConstancyProperty.lb) + return Result(data, StringConstancyProperty.lb) // TODO add continuation } else { tac = tacaiEOptP.ub.tac.get } @@ -101,8 +101,9 @@ class IntraproceduralStringAnalysis( val cfg = tac.cfg val stmts = tac.stmts - val uvar = data._1 - val defSites = uvar.definedBy.toArray.sorted + val puVar = data._1 + val uVar = puVar.toValueOriginForm(tac.pcToIndex) + 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) { @@ -119,17 +120,17 @@ class IntraproceduralStringAnalysis( val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) + 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) } - val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) - val leanPaths = paths.makeLeanPath(uvar, stmts) + 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) + val dependentVars = findDependentVars(leanPaths, stmts, puVar)(tac) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar => val toAnalyze = (nextVar, data._2) @@ -156,7 +157,7 @@ class IntraproceduralStringAnalysis( else { val interHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( - uvar.definedBy.toArray.sorted.map { ds => + uVar.definedBy.toArray.sorted.map { ds => val r = interHandler.processDefSite(ds).asFinal r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } @@ -243,16 +244,16 @@ class IntraproceduralStringAnalysis( * [[FlatPathElement.element]] in which it occurs. */ private def findDependeesAcc( - subpath: SubPath, - stmts: Array[Stmt[SEntity]], - target: SEntity, - foundDependees: ListBuffer[(SEntity, Int)], - hasTargetBeenSeen: Boolean - ): (ListBuffer[(SEntity, Int)], Boolean) = { + subpath: SubPath, + stmts: Array[Stmt[V]], + target: SEntity, + foundDependees: ListBuffer[(SEntity, Int)], + hasTargetBeenSeen: Boolean + )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false subpath match { case fpe: FlatPathElement => - if (target.definedBy.contains(fpe.element)) { + if (target.toValueOriginForm(tac.pcToIndex).definedBy.contains(fpe.element)) { encounteredTarget = true } // For FlatPathElements, search for DUVars on which the toString method is called @@ -265,7 +266,7 @@ class IntraproceduralStringAnalysis( val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append(( - outerExpr.asVirtualFunctionCall.params.head.asVar, + outerExpr.asVirtualFunctionCall.params.head.asVar.toPersistentForm(tac.stmts), fpe.element )) } @@ -303,12 +304,12 @@ class IntraproceduralStringAnalysis( * this variable as `ignore`. */ private def findDependentVars( - path: Path, - stmts: Array[Stmt[SEntity]], - ignore: SEntity - ): mutable.LinkedHashMap[SEntity, Int] = { + path: Path, + stmts: Array[Stmt[V]], + ignore: SEntity + )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() - val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) + val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), stmts) var wasTargetSeen = false path.elements.foreach { nextSubpath => @@ -322,7 +323,10 @@ class IntraproceduralStringAnalysis( ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => - val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + val newExpressions = InterpretationHandler.findNewOfVar( + nextPair._1.toValueOriginForm(tac.pcToIndex), + stmts + ) if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) } 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 26b67662cf..bc583cba61 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 @@ -38,7 +38,7 @@ import org.opalj.value.ValueInformation * @author Patrick Mell */ abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + protected val cfg: CFG[Stmt[V], TACStmts[V]], protected val exprHandler: InterpretationHandler ) { @@ -59,7 +59,7 @@ abstract class AbstractStringInterpreter( ps: PropertyStore, m: Method, s: InterproceduralComputationState - ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, SEntity]]) = { + ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { (tacai, tacai.ub.tac) @@ -102,12 +102,12 @@ abstract class AbstractStringInterpreter( protected def getParametersForPCs( pcs: Iterable[Int], tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): List[Seq[Expr[SEntity]]] = { - val paramLists = ListBuffer[Seq[Expr[SEntity]]]() + ): 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[SEntity]) => vfc.params - case Assignment(_, _, fc: FunctionCall[SEntity]) => fc.params + case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params + case Assignment(_, _, fc: FunctionCall[V]) => fc.params case _ => Seq() } if (params.nonEmpty) { @@ -130,11 +130,11 @@ abstract class AbstractStringInterpreter( * entities to functions, `entity2function`. */ protected def evaluateParameters( - params: List[Seq[Expr[SEntity]]], + params: List[Seq[Expr[V]]], iHandler: InterproceduralInterpretationHandler, - funCall: FunctionCall[SEntity], + funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, - entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[SEntity]]] + entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] ): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { 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 d3771495d1..5b44f5d9d9 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 @@ -81,7 +81,7 @@ object InterpretationHandler { * @return Returns true if `expr` is a call to `toString` of [[StringBuilder]] or * [[StringBuffer]]. */ - def isStringBuilderBufferToStringCall(expr: Expr[SEntity]): Boolean = + def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { case VirtualFunctionCall(_, clazz, _, "toString", _, _, _) => val className = clazz.mostPreciseObjectType.fqn @@ -96,7 +96,7 @@ object InterpretationHandler { * @return Returns `true` if the given expression is a string constant / literal and `false` * otherwise. */ - def isStringConstExpression(expr: Expr[SEntity]): Boolean = if (expr.isStringConst) { + def isStringConstExpression(expr: Expr[V]): Boolean = if (expr.isStringConst) { true } else { if (expr.isVar) { @@ -112,7 +112,7 @@ object InterpretationHandler { /** * Returns `true` if the given expressions is a primitive number type */ - def isPrimitiveNumberTypeExpression(expr: Expr[SEntity]): Boolean = + def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = expr.asVar.value.isPrimitiveValue && InterproceduralStringAnalysis.isSupportedPrimitiveNumberType( expr.asVar.value.asPrimitiveValue.primitiveType.toJava @@ -126,7 +126,7 @@ object InterpretationHandler { * @return Returns true if `expr` is a call to `append` of [[StringBuilder]] or * [[StringBuffer]]. */ - def isStringBuilderBufferAppendCall(expr: Expr[SEntity]): Boolean = { + def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) => val className = clazz.toJavaClass.getName @@ -140,8 +140,8 @@ object InterpretationHandler { * Helper function for [[findDefSiteOfInit]]. */ private def findDefSiteOfInitAcc( - toString: VirtualFunctionCall[SEntity], - stmts: Array[Stmt[SEntity]] + toString: VirtualFunctionCall[V], + stmts: Array[Stmt[V]] ): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder if (toString.name != "toString") { @@ -154,11 +154,11 @@ object InterpretationHandler { while (stack.nonEmpty) { val next = stack.pop() stmts(next) match { - case a: Assignment[SEntity] => + case a: Assignment[V] => a.expr match { case _: New => defSites.append(next) - case vfc: VirtualFunctionCall[SEntity] => + case vfc: VirtualFunctionCall[V] => 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 @@ -167,7 +167,7 @@ object InterpretationHandler { } else { defSites.append(next) } - case _: GetField[SEntity] => + case _: GetField[V] => defSites.append(next) case _ => // E.g., NullExpr } @@ -187,18 +187,18 @@ object InterpretationHandler { * @param stmts The search context for finding the relevant information. * @return Returns the definition sites of the base object. */ - def findDefSiteOfInit(duvar: SEntity, stmts: Array[Stmt[SEntity]]): List[Int] = { + def findDefSiteOfInit(value: V, stmts: Array[Stmt[V]]): List[Int] = { val defSites = ListBuffer[Int]() - duvar.definedBy.foreach { ds => + value.definedBy.foreach { ds => defSites.appendAll(stmts(ds).asAssignment.expr match { - case vfc: VirtualFunctionCall[SEntity] => findDefSiteOfInitAcc(vfc, stmts) + case vfc: VirtualFunctionCall[V] => findDefSiteOfInitAcc(vfc, stmts) // The following case is, e.g., for {NonVirtual, Static}FunctionCalls 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.appendAll(value.definedBy.toArray) } defSites.distinct.sorted.toList @@ -211,22 +211,22 @@ object InterpretationHandler { * @param stmts The context to search in, e.g., the surrounding method. * @return Returns all found [[New]] expressions. */ - def findNewOfVar(duvar: SEntity, stmts: Array[Stmt[SEntity]]): List[New] = { + def findNewOfVar(value: V, stmts: Array[Stmt[V]]): List[New] = { val news = ListBuffer[New]() // HINT: It might be that the search has to be extended to further cases - duvar.definedBy.filter(_ >= 0).foreach { ds => + value.definedBy.filter(_ >= 0).foreach { ds => stmts(ds) match { // E.g., a call to `toString` or `append` - case Assignment(_, _, vfc: VirtualFunctionCall[SEntity]) => + case Assignment(_, _, vfc: VirtualFunctionCall[V]) => vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs => stmts(innerDs) match { case Assignment(_, _, expr: New) => news.append(expr) - case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => + case Assignment(_, _, expr: VirtualFunctionCall[V]) => val exprReceiverVar = expr.receiver.asVar // The "if" is to avoid endless recursion - if (duvar.definedBy != exprReceiverVar.definedBy) { + if (value.definedBy != exprReceiverVar.definedBy) { news.appendAll(findNewOfVar(exprReceiverVar, stmts)) } case _ => 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 3060bc1d3d..046a27fb07 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 @@ -27,11 +27,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class BinaryExprInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = BinaryExpr[SEntity] + override type T = BinaryExpr[V] /** * Currently, this implementation supports the interpretation of the following binary 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 a728859776..c67d69e50c 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 @@ -26,7 +26,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class DoubleValueInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 321f412015..c5ca41e026 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 @@ -26,7 +26,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class FloatValueInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 d2111da093..d7ddbf209c 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 @@ -26,7 +26,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntegerValueInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 00081f0d49..e2dea6f1c2 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 @@ -23,7 +23,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 3144575c20..718715f79a 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 @@ -27,7 +27,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class StringConstInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 70345d753d..b44f32ad12 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 @@ -30,13 +30,13 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class ArrayPreparationInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = ArrayLoad[SEntity] + override type T = ArrayLoad[V] /** * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string @@ -59,10 +59,8 @@ class ArrayPreparationInterpreter( allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, ep) => - if (ep.isFinal) { - val p = ep.asFinal.p.asInstanceOf[StringConstancyProperty] - state.appendToFpe2Sci(ds, p.stringConstancyInformation) - } + if (ep.isFinal) + state.appendToFpe2Sci(ds, ep.asFinal.p.stringConstancyInformation) results.append(ep) } @@ -82,7 +80,7 @@ class ArrayPreparationInterpreter( interims.get } else { var resultSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + _.asFinal.p.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 @@ -105,7 +103,7 @@ class ArrayPreparationInterpreter( object ArrayPreparationInterpreter { - type T = ArrayLoad[SEntity] + type T = ArrayLoad[V] /** * This function retrieves all definition sites of the array stores and array loads that belong @@ -116,7 +114,7 @@ object ArrayPreparationInterpreter { * @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, stmts: Array[Stmt[SEntity]]): List[Int] = { + def getStoreAndLoadDefSites(instr: T, stmts: Array[Stmt[V]]): List[Int] = { val allDefSites = ListBuffer[Int]() val defSites = instr.arrayRef.asVar.definedBy.toArray @@ -125,12 +123,12 @@ object ArrayPreparationInterpreter { val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted // For ArrayStores sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[SEntity]] + 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[SEntity]) => true + case Assignment(_, _, _: ArrayLoad[V]) => true case _ => false } } foreach { f: Int => 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 f92ec2a7ac..7d2e587498 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 @@ -20,7 +20,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.cg.uVarForDefSites /** * The `InterproceduralFieldInterpreter` is responsible for processing instances of [[FieldRead]]s. @@ -40,7 +39,7 @@ class InterproceduralFieldInterpreter( implicit val contextProvider: ContextProvider ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { - override type T = FieldRead[SEntity] + override type T = FieldRead[V] /** * Currently, fields are approximated using the following approach. If a field of a type not @@ -88,7 +87,7 @@ class InterproceduralFieldInterpreter( } else { tac match { case Some(methodTac) => - val entity = (uVarForDefSites(parameter.get, methodTac.pcToIndex), method) + val entity = (PUVar(parameter.get._1, parameter.get._2), method) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { state.dependees = eps :: state.dependees 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 82b97ec448..bf421318f6 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 @@ -93,26 +93,24 @@ class InterproceduralInterpretationHandler( 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[SEntity]) => - processArrayLoad(expr, defSite, params) - case Assignment(_, _, expr: NewArray[SEntity]) => - processNewArray(expr, defSite, params) + 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) - case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => processVFC(expr, defSite, params) - case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => processVFC(expr, defSite, params) - case Assignment(_, _, expr: StaticFunctionCall[SEntity]) => + 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, params) - case ExprStmt(_, expr: StaticFunctionCall[SEntity]) => + case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite, params) - case Assignment(_, _, expr: BinaryExpr[SEntity]) => processBinaryExpr(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[SEntity]) => + case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) - case Assignment(_, _, expr: GetField[SEntity]) => processGetField(expr, defSite) - case vmc: VirtualMethodCall[SEntity] => + case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) + case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) - case nvmc: NonVirtualMethodCall[SEntity] => processNonVirtualMethodCall(nvmc, defSite) + case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) @@ -147,7 +145,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[ArrayLoad]]s. */ private def processArrayLoad( - expr: ArrayLoad[SEntity], + expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { @@ -171,7 +169,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NewArray]]s. */ private def processNewArray( - expr: NewArray[SEntity], + expr: NewArray[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { @@ -209,7 +207,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ private def processVFC( - expr: VirtualFunctionCall[SEntity], + expr: VirtualFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { @@ -257,7 +255,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[SEntity], + expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { @@ -282,7 +280,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[BinaryExpr]]s. */ private def processBinaryExpr( - expr: BinaryExpr[SEntity], + expr: BinaryExpr[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such @@ -298,7 +296,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField( - expr: FieldRead[SEntity], + expr: FieldRead[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( @@ -320,7 +318,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualFunctionCall( - expr: NonVirtualFunctionCall[SEntity], + expr: NonVirtualFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( @@ -342,7 +340,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[VirtualMethodCall]]s. */ def processVirtualMethodCall( - expr: VirtualMethodCall[SEntity], + expr: VirtualMethodCall[V], defSite: Int, callees: Callees ): EOptionP[Entity, StringConstancyProperty] = { @@ -359,7 +357,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualMethodCall( - nvmc: NonVirtualMethodCall[SEntity], + nvmc: NonVirtualMethodCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( @@ -425,23 +423,23 @@ class InterproceduralInterpretationHandler( state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) } else { stmts(defSite) match { - case nvmc: NonVirtualMethodCall[SEntity] => + case nvmc: NonVirtualMethodCall[V] => NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) - case Assignment(_, _, al: ArrayLoad[SEntity]) => + case Assignment(_, _, al: ArrayLoad[V]) => ArrayLoadFinalizer(state, cfg).finalizeInterpretation(al, defSite) - case Assignment(_, _, na: NewArray[SEntity]) => + case Assignment(_, _, na: NewArray[V]) => NewArrayFinalizer(state, cfg).finalizeInterpretation(na, defSite) - case Assignment(_, _, vfc: VirtualFunctionCall[SEntity]) => + case Assignment(_, _, vfc: VirtualFunctionCall[V]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case ExprStmt(_, vfc: VirtualFunctionCall[SEntity]) => + case ExprStmt(_, vfc: VirtualFunctionCall[V]) => VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case Assignment(_, _, fr: FieldRead[SEntity]) => + case Assignment(_, _, fr: FieldRead[V]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case ExprStmt(_, fr: FieldRead[SEntity]) => + case ExprStmt(_, fr: FieldRead[V]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) - case Assignment(_, _, sfc: StaticFunctionCall[SEntity]) => + case Assignment(_, _, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) - case ExprStmt(_, sfc: StaticFunctionCall[SEntity]) => + case ExprStmt(_, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case _ => state.appendToFpe2Sci( 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 e2b59837a5..d82127e48a 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 @@ -27,7 +27,7 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, @@ -35,7 +35,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualFunctionCall[SEntity] + override type T = NonVirtualFunctionCall[V] /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns @@ -60,20 +60,20 @@ class InterproceduralNonVirtualFunctionCallInterpreter( if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVars and start the string analysis - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) + 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 FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val uvar = ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar - val entity = (uvar, m) + val puVar = ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts) + val entity = (puVar, m) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(uvar, defSite) + state.appendToVar2IndexMapping(puVar, defSite) } eps } 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 2e70bd7bfe..169112cfad 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,14 +26,14 @@ import org.opalj.fpcf.PropertyStore * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualMethodCall[SEntity] + override type T = NonVirtualMethodCall[V] /** * Currently, this function supports the interpretation of the following non virtual methods: @@ -51,7 +51,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[SEntity], + instr: T, defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite @@ -69,7 +69,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( * these are currently interpreted). */ private def interpretInit( - init: NonVirtualMethodCall[SEntity], + init: T, defSite: Integer ): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { @@ -81,8 +81,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( if (results.forall(_._2.isFinal)) { // Final result is available val reduced = StringConstancyInformation.reduceMultiple(results.map { r => - val prop = r._2.asFinal.p.asInstanceOf[StringConstancyProperty] - prop.stringConstancyInformation + r._2.asFinal.p.stringConstancyInformation }) FinalEP(defSite, StringConstancyProperty(reduced)) } else { @@ -92,10 +91,9 @@ class InterproceduralNonVirtualMethodCallInterpreter( results.foreach { case (ds, r) => if (r.isFinal) { - val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] state.appendToFpe2Sci( ds, - p.stringConstancyInformation, + r.asFinal.p.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 1cff691b6c..e74a36340e 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 @@ -32,16 +32,16 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]], + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = StaticFunctionCall[SEntity] + override type T = StaticFunctionCall[V] /** * This function always returns a list with a single element consisting of @@ -70,7 +70,7 @@ class InterproceduralStaticFunctionCallInterpreter( * the parameter passed to the call. */ private def processStringValueOf( - call: StaticFunctionCall[SEntity] + call: StaticFunctionCall[V] ): EOptionP[Entity, StringConstancyProperty] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds => exprHandler.processDefSite(ds, params) @@ -80,7 +80,7 @@ class InterproceduralStaticFunctionCallInterpreter( interim.get } else { // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r => r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } + val scis = results.map { r => r.asFinal.p.stringConstancyInformation } val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { scis.map { sci => if (Try(sci.possibleStrings.toInt).isSuccess) { @@ -101,7 +101,7 @@ class InterproceduralStaticFunctionCallInterpreter( * This function interprets an arbitrary static function call. */ private def processArbitraryCall( - instr: StaticFunctionCall[SEntity], + instr: StaticFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC( @@ -147,9 +147,9 @@ class InterproceduralStaticFunctionCallInterpreter( val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (nonFinalResults.nonEmpty) { if (tac.isDefined) { - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) returns.foreach { ret => - val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, m) + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), m) val eps = ps(entity, StringConstancyProperty.key) state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) @@ -166,14 +166,14 @@ class InterproceduralStaticFunctionCallInterpreter( if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) + 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 FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, m) + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), m) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(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 b82832b8a9..862c9d7af0 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 @@ -27,12 +27,12 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class InterproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, callees: Callees ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualMethodCall[SEntity] + override type T = VirtualMethodCall[V] /** * Currently, this function supports the interpretation of the following virtual methods: 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 b10e3bd2a1..8d861526ba 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 @@ -27,13 +27,13 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewArrayPreparer( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NewArray[SEntity] + override type T = NewArray[V] /** * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string @@ -55,7 +55,7 @@ class NewArrayPreparer( 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[SEntity]] + 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 => @@ -82,7 +82,7 @@ class NewArrayPreparer( interims.get } else { var resultSci = StringConstancyInformation.reduceMultiple(allResults.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + _.asFinal.p.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 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 cfeaccd8ba..41e70570b4 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 @@ -35,7 +35,7 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class VirtualFunctionCallPreparationInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, @@ -44,7 +44,7 @@ class VirtualFunctionCallPreparationInterpreter( typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualFunctionCall[SEntity] + override type T = VirtualFunctionCall[V] /** * Currently, this implementation supports the interpretation of the following function calls: @@ -158,14 +158,14 @@ class VirtualFunctionCallPreparationInterpreter( val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { state.methodPrep2defSite.remove(nextMethod) - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[SEntity]]) + 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 FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[SEntity]].expr.asVar, nextMethod) + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { @@ -201,7 +201,7 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[SEntity], + appendCall: VirtualFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) @@ -264,7 +264,7 @@ class VirtualFunctionCallPreparationInterpreter( * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[SEntity], + call: VirtualFunctionCall[V], state: InterproceduralComputationState ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -298,7 +298,7 @@ class VirtualFunctionCallPreparationInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[SEntity], + call: VirtualFunctionCall[V], state: InterproceduralComputationState ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append @@ -378,7 +378,7 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretToStringCall( - call: VirtualFunctionCall[SEntity] + call: VirtualFunctionCall[V] ): EOptionP[Entity, StringConstancyProperty] = // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) @@ -389,7 +389,7 @@ class VirtualFunctionCallPreparationInterpreter( * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( - instr: VirtualFunctionCall[SEntity] + instr: VirtualFunctionCall[V] ): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 114979a313..a4e2b9260d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -18,10 +18,10 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation */ class ArrayLoadFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { - override type T = ArrayLoad[SEntity] + override type T = ArrayLoad[V] /** * Finalizes [[ArrayLoad]]s. @@ -51,7 +51,7 @@ object ArrayLoadFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ): ArrayLoadFinalizer = new ArrayLoadFinalizer(state, cfg) } 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 9d86275a0d..286b0a277c 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 @@ -12,7 +12,7 @@ class GetFieldFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override protected type T = FieldRead[SEntity] + override protected type T = FieldRead[V] /** * Finalizes [[FieldRead]]s. 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 index f88a5b82e4..1f618fe2e1 100644 --- 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 @@ -15,10 +15,10 @@ import org.opalj.br.cfg.CFG */ class NewArrayFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { - override type T = NewArray[SEntity] + override type T = NewArray[V] /** * Finalizes [[NewArray]]s. @@ -35,7 +35,7 @@ object NewArrayFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ): NewArrayFinalizer = new NewArrayFinalizer(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 31356a0a86..a37d789916 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 @@ -18,7 +18,7 @@ class NonVirtualMethodCallFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override type T = NonVirtualMethodCall[SEntity] + override type T = NonVirtualMethodCall[V] /** * Finalizes [[NonVirtualMethodCall]]s. 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 index 0919f59836..5ea0b2cfcc 100644 --- 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 @@ -18,7 +18,7 @@ class StaticFunctionCallFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override type T = StaticFunctionCall[SEntity] + override type T = StaticFunctionCall[V] /** * Finalizes [[StaticFunctionCall]]s. 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 eecb8ef28f..09c4fa0e8d 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 @@ -19,10 +19,10 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType */ class VirtualFunctionCallFinalizer( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { - override type T = VirtualFunctionCall[SEntity] + override type T = VirtualFunctionCall[V] /** * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" and @@ -121,7 +121,7 @@ object VirtualFunctionCallFinalizer { def apply( state: InterproceduralComputationState, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) } 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 254f188546..1da90acc1f 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 @@ -25,11 +25,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralArrayInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = ArrayLoad[SEntity] + override type T = ArrayLoad[V] /** * @note For this implementation, `defSite` does not play a role. @@ -46,7 +46,7 @@ class IntraproceduralArrayInterpreter( val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted // Process ArrayStores sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[SEntity]] + 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 => @@ -56,7 +56,7 @@ class IntraproceduralArrayInterpreter( // Process ArrayLoads sortedArrDeclUses.filter { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[SEntity]) => true + case Assignment(_, _, _: ArrayLoad[V]) => true case _ => false } } foreach { f: Int => 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 6d0d8271b5..163c20f28b 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 @@ -24,11 +24,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralFieldInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = GetField[SEntity] + override type T = GetField[V] /** * Fields are not suppoerted by this implementation. Thus, this function always returns a result 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 96c791ddec..8fee90d3b1 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 @@ -24,7 +24,7 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralGetStaticInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { 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 e946a463a4..0a43050ab0 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,36 +65,36 @@ class IntraproceduralInterpretationHandler( new FloatValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: DoubleConst) => new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: ArrayLoad[SEntity]) => + case Assignment(_, _, expr: ArrayLoad[V]) => new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: New) => new NewInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: VirtualFunctionCall[SEntity]) => + case Assignment(_, _, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: StaticFunctionCall[SEntity]) => + case Assignment(_, _, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[SEntity]) => + case Assignment(_, _, expr: BinaryExpr[V]) => new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[SEntity]) => + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => new IntraproceduralNonVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case Assignment(_, _, expr: GetField[SEntity]) => + case Assignment(_, _, expr: GetField[V]) => new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) - case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => + case ExprStmt(_, expr: VirtualFunctionCall[V]) => new IntraproceduralVirtualFunctionCallInterpreter( cfg, this ).interpret(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[SEntity]) => + case ExprStmt(_, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case vmc: VirtualMethodCall[SEntity] => + case vmc: VirtualMethodCall[V] => new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) - case nvmc: NonVirtualMethodCall[SEntity] => + case nvmc: NonVirtualMethodCall[V] => new IntraproceduralNonVirtualMethodCallInterpreter( cfg, this 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 4969a59e09..dc521991c3 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 @@ -22,11 +22,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualFunctionCall[SEntity] + override type T = NonVirtualFunctionCall[V] /** * This function always returns a result that contains [[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 69d0d8a0a9..f64feba46e 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 @@ -26,11 +26,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = NonVirtualMethodCall[SEntity] + override type T = NonVirtualMethodCall[V] /** * Currently, this function supports the interpretation of the following non virtual methods: @@ -50,7 +50,7 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret( - instr: NonVirtualMethodCall[SEntity], + instr: T, defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { @@ -67,7 +67,7 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit(init: NonVirtualMethodCall[SEntity]): StringConstancyProperty = { + private def interpretInit(init: T): StringConstancyProperty = { init.params.size match { case 0 => StringConstancyProperty.getNeutralElement case _ => 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 5bc43d01f4..9753744822 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 @@ -24,11 +24,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = StaticFunctionCall[SEntity] + override type T = StaticFunctionCall[V] /** * This function always returns a result containing [[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 c45f51ad0c..7a06e2f60b 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 @@ -32,11 +32,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualFunctionCall[SEntity] + override type T = VirtualFunctionCall[V] /** * Currently, this implementation supports the interpretation of the following function calls: @@ -93,7 +93,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[SEntity] + appendCall: VirtualFunctionCall[V] ): StringConstancyProperty = { val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation @@ -130,7 +130,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * This function determines the current value of the receiver object of an `append` call. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[SEntity] + 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 @@ -149,7 +149,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[SEntity] + call: VirtualFunctionCall[V] ): StringConstancyProperty = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append @@ -200,7 +200,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretToStringCall( - call: VirtualFunctionCall[SEntity] + call: VirtualFunctionCall[V] ): StringConstancyProperty = { val finalEP = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asFinal finalEP.p.asInstanceOf[StringConstancyProperty] @@ -212,7 +212,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( - instr: VirtualFunctionCall[SEntity] + instr: VirtualFunctionCall[V] ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace } 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 58152aca1d..afdb008523 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 @@ -26,11 +26,11 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { - override type T = VirtualMethodCall[SEntity] + override type T = VirtualMethodCall[V] /** * Currently, this function supports the interpretation of the following virtual methods: 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 d3f4709a62..50df27a434 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 @@ -22,7 +22,7 @@ import org.opalj.br.cfg.CFGNode * * @author Patrick Mell */ -abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { +abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { /** * CSInfo stores information regarding control structures (CS) in the form: Index of the start @@ -79,7 +79,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { }.max var containsIf = false for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { processedIfs(i) = () containsIf = true } @@ -134,11 +134,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { } case _ => // No goto available => Jump after next block - var nextIf: Option[If[SEntity]] = None + var nextIf: Option[If[V]] = None var i = nextBlock while (i < cfg.code.instructions.length && nextIf.isEmpty) { cfg.code.instructions(i) match { - case iff: If[SEntity] => + case iff: If[V] => nextIf = Some(iff) processedIfs(i) = () case _ => @@ -189,11 +189,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { }.max var nextIfIndex = -1 - val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[SEntity]].targetStmt + val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt for (i <- cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { // The second condition is necessary to detect two consecutive "if"s (not in an else-if // relation) - if (cfg.code.instructions(i).isInstanceOf[If[SEntity]] && ifTarget != i) { + if (cfg.code.instructions(i).isInstanceOf[If[V]] && ifTarget != i) { nextIfIndex = i } } @@ -246,7 +246,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { cfg.bb(ifTarget).predecessors.foreach { case pred: BasicBlock => for (i <- pred.startPC.to(pred.endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { processedIfs(i) = () } } @@ -268,7 +268,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { var returnPos = startPos var foundReturn = false while (!foundReturn && returnPos < cfg.code.instructions.length) { - if (cfg.code.instructions(returnPos).isInstanceOf[ReturnValue[SEntity]]) { + if (cfg.code.instructions(returnPos).isInstanceOf[ReturnValue[V]]) { foundReturn = true } else { returnPos += 1 @@ -451,7 +451,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { nextBlock = popped + 1 while (nextBlock < cfg.code.instructions.length - 1 && - !cfg.code.instructions(nextBlock).isInstanceOf[If[SEntity]] + !cfg.code.instructions(nextBlock).isInstanceOf[If[V]] ) { nextBlock += 1 } @@ -459,7 +459,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { var containsIf = false for (i <- cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[SEntity]]) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { containsIf = true } } @@ -643,11 +643,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { // 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[SEntity]] = None + var iff: Option[If[V]] = None var i = indexFirstAfterCatch while (iff.isEmpty) { cfg.code.instructions(i) match { - case foundIf: If[SEntity] => iff = Some(foundIf) + case foundIf: If[V] => iff = Some(foundIf) case _ => } i += 1 @@ -708,7 +708,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { protected def isHeadOfLoop( site: Int, loops: List[List[Int]], - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]] + cfg: CFG[Stmt[V], TACStmts[V]] ): Boolean = { var belongsToLoopHeader = false @@ -730,7 +730,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { if (!belongsToLoopHeader) { val start = nextLoop.head var end = start - while (!cfg.code.instructions(end).isInstanceOf[If[SEntity]]) { + while (!cfg.code.instructions(end).isInstanceOf[If[V]]) { end += 1 } if (site >= start && site <= end && end < nextLoop.last) { @@ -781,7 +781,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { */ protected def isCondWithoutElse( branchingSite: Int, - cfg: CFG[Stmt[SEntity], TACStmts[SEntity]], + cfg: CFG[Stmt[V], TACStmts[V]], processedIfs: mutable.Map[Int, Unit] ): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors @@ -813,7 +813,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { val indexIf = cfg.bb(lastEle) match { case bb: BasicBlock => val ifPos = bb.startPC.to(bb.endPC).filter( - cfg.code.instructions(_).isInstanceOf[If[SEntity]] + cfg.code.instructions(_).isInstanceOf[If[V]] ) if (ifPos.nonEmpty && !isHeadOfLoop(ifPos.head, cfg.findNaturalLoops(), cfg)) { ifPos.head @@ -1057,10 +1057,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { case bb: BasicBlock => for (i <- bb.startPC.to(bb.endPC)) { cfg.code.instructions(i) match { - case _: If[SEntity] if !processedIfs.contains(i) => + case _: If[V] if !processedIfs.contains(i) => foundCS.append(processIf(i, processedIfs)) processedIfs(i) = () - case _: Switch[SEntity] if !processedSwitches.contains(i) => + case _: Switch[V] if !processedSwitches.contains(i) => foundCS.append(processSwitch(i)) processedSwitches(i) = () case _ => @@ -1126,7 +1126,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) { val element = toTransform.hierarchy.head._1.get val start = element._1 val end = element._2 - if (cfg.code.instructions(start).isInstanceOf[Switch[SEntity]]) { + if (cfg.code.instructions(start).isInstanceOf[Switch[V]]) { buildPathForSwitch(start, end, fill) } else { element._3 match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index 9cdfa97cf2..d2a0b866b5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -23,7 +23,7 @@ import org.opalj.br.cfg.CFG * jumps within the bytecode might lead to a different order than the one computed by this * class! */ -class DefaultPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) extends AbstractPathFinder(cfg) { +class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` 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 1e0e3658e8..fbbf9fb5a7 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 @@ -83,7 +83,7 @@ case class Path(elements: List[SubPath]) { */ private def getAllDefAndUseSites( obj: DUVar[ValueInformation], - stmts: Array[Stmt[SEntity]] + stmts: Array[Stmt[V]] ): List[Int] = { val defAndUses = ListBuffer[Int]() val stack = mutable.Stack[Int](obj.definedBy.toList: _*) @@ -94,12 +94,12 @@ case class Path(elements: List[SubPath]) { defAndUses.append(popped) stmts(popped) match { - case a: Assignment[SEntity] if a.expr.isInstanceOf[VirtualFunctionCall[SEntity]] => + case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] => 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[SEntity] if a.expr.isInstanceOf[New] => + case a: Assignment[V] if a.expr.isInstanceOf[New] => stack.pushAll(a.targetVar.usedBy.toArray) case _ => } @@ -266,15 +266,15 @@ case class Path(elements: List[SubPath]) { * 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[SEntity]]): Path = { + def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Path = { 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[SEntity]) => + case Assignment(_, _, expr: VirtualFunctionCall[V]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) - case ExprStmt(_, expr: VirtualFunctionCall[SEntity]) => + case ExprStmt(_, expr: VirtualFunctionCall[V]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) case _ => true diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index 9c7dd30ea4..c7e89498db 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -22,7 +22,7 @@ import org.opalj.br.cfg.CFG * jumps within the bytecode might lead to a different order than the one computed by this * class! */ -class WindowPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) extends AbstractPathFinder(cfg) { +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` @@ -42,9 +42,9 @@ class WindowPathFinder(cfg: CFG[Stmt[SEntity], TACStmts[SEntity]]) extends Abstr var nextStmt = startSites.min while (nextStmt >= 0 && startSite.isEmpty) { cfg.code.instructions(nextStmt) match { - case iff: If[SEntity] if startSites.contains(iff.targetStmt) => + case iff: If[V] if startSites.contains(iff.targetStmt) => startSite = Some(nextStmt) - case _: Switch[SEntity] => + case _: Switch[V] => val (startSwitch, endSwitch, _) = processSwitch(nextStmt) val isParentSwitch = startSites.forall { nextStartSite => nextStartSite >= startSwitch && nextStartSite <= endSwitch 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 d12b519a47..9d281d23cb 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,7 +11,6 @@ import org.opalj.br.Method import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.value.ValueInformation /** * @author Patrick Mell @@ -23,11 +22,11 @@ package object string_analysis { * * @note The analysis require further context information, see [[SContext]]. */ - type SEntity = DUVar[ValueInformation] + type SEntity = PV /** * [[IntraproceduralStringAnalysis]] and [[InterproceduralStringAnalysis]] process a local variable within a - * particular context, i.e. the method in which it is used. TODO should this be "context"? + * particular context, i.e. the method in which it is used. */ type SContext = (SEntity, Method) @@ -47,6 +46,6 @@ package object string_analysis { * necessary to uniquely identify a position as an entity might be used for different function * calls. */ - type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[SEntity], mutable.Map[SContext, (Int, Int, Int)]] + type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[SContext, (Int, Int, Int)]] } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala index 9e7209e21d..846a97c8ea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala @@ -31,6 +31,7 @@ import org.opalj.value.ValueInformation package object tac { type V = DUVar[ValueInformation] + type PV = PDUVar[ValueInformation] final def pcOfDefSite(valueOrigin: ValueOrigin)(implicit stmts: Array[Stmt[V]]): Int = { if (valueOrigin >= 0) From 938742df986297c96c88776d13f924c44f3aa498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 13:42:29 +0100 Subject: [PATCH 334/583] Refactor tests --- .../InterproceduralTestMethods.java | 9 +- .../{ => interprocedural}/StringProvider.java | 2 +- .../hierarchies/GreetingService.java | 2 +- .../hierarchies/HelloGreeting.java | 2 +- .../hierarchies/SimpleHelloGreeting.java | 2 +- .../IntraProceduralTestMethods.java} | 4 +- .../string_analysis/StringDefinitions.java | 3 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 212 +++++------------- 8 files changed, 72 insertions(+), 164 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{ => interprocedural}/InterproceduralTestMethods.java (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{ => interprocedural}/StringProvider.java (81%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{ => interprocedural}/hierarchies/GreetingService.java (64%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{ => interprocedural}/hierarchies/HelloGreeting.java (73%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{ => interprocedural}/hierarchies/SimpleHelloGreeting.java (73%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{LocalTestMethods.java => intraprocedural/IntraProceduralTestMethods.java} (99%) 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/interprocedural/InterproceduralTestMethods.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java index 47cf5c7d3f..9561a53aeb 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/interprocedural/InterproceduralTestMethods.java @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string_analysis.interprocedural; -import org.opalj.fpcf.fixtures.string_analysis.hierarchies.GreetingService; -import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; +import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.GreetingService; +import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.HelloGreeting; +import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -16,7 +17,7 @@ /** * This file contains various tests for the InterproceduralStringAnalysis. For further information - * on what to consider, please see {@link LocalTestMethods} + * on what to consider, please see {@link IntraProceduralTestMethods} * * @author Patrick Mell */ 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/interprocedural/StringProvider.java similarity index 81% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/StringProvider.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/StringProvider.java index 59ba4b4573..71826969da 100644 --- 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/interprocedural/StringProvider.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string_analysis.interprocedural; public class StringProvider { 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/interprocedural/hierarchies/GreetingService.java similarity index 64% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/GreetingService.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/GreetingService.java index 65ecf9b691..efa32e9d7e 100644 --- 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/interprocedural/hierarchies/GreetingService.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; public interface GreetingService { 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/interprocedural/hierarchies/HelloGreeting.java similarity index 73% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/HelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/HelloGreeting.java index ca9cb0c921..c17d033516 100644 --- 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/interprocedural/hierarchies/HelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; public class HelloGreeting implements GreetingService { 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/interprocedural/hierarchies/SimpleHelloGreeting.java similarity index 73% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/SimpleHelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/SimpleHelloGreeting.java index af86ec1dab..e0e6db9229 100644 --- 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/interprocedural/hierarchies/SimpleHelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; public class SimpleHelloGreeting implements GreetingService { 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/intraprocedural/IntraProceduralTestMethods.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index 72a90da283..012f220894 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/intraprocedural/IntraProceduralTestMethods.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string_analysis.intraprocedural; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -57,7 +57,7 @@ * * @author Patrick Mell */ -public class LocalTestMethods { +public class IntraProceduralTestMethods { private String someStringField = ""; public static final String MY_CONSTANT = "mine"; 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 d8512e51ce..de77ccd8a9 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 @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.properties.string_analysis; +import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods; import org.opalj.fpcf.properties.PropertyValidator; import java.lang.annotation.*; @@ -32,7 +33,7 @@ /** * A regexp like string that describes the element(s) that are expected. For the rules, refer to - * {@link org.opalj.fpcf.fixtures.string_analysis.LocalTestMethods}. + * {@link IntraProceduralTestMethods}. * 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/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 5a06472939..57bb52461b 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 @@ -2,16 +2,11 @@ package org.opalj package fpcf -import java.io.File import java.net.URL -import scala.collection.mutable.ListBuffer import org.opalj.br.Annotation import org.opalj.br.Annotations import org.opalj.br.Method import org.opalj.br.analyses.Project -import org.opalj.br.fpcf.FPCFAnalysesManagerKey -import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.TACMethodParameter @@ -23,30 +18,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnal import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.SEntity -/** - * @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. - */ -sealed class StringAnalysisTestRunner( - val fqTestMethodsClass: String, - val nameTestMethod: String, - val filesToLoad: List[String] -) extends PropertiesTest { +sealed abstract class StringAnalysisTest extends PropertiesTest { - /** - * @return Returns all relevant project files (NOT including library files) to run the tests. - */ - def getRelevantProjectFiles: Array[File] = { - val necessaryFiles = Array( - "properties/string_analysis/StringDefinitions.class" - ) ++ filesToLoad - val basePath = System.getProperty("user.dir") + - "/DEVELOPING_OPAL/validate/target/scala-2.13/test-classes/org/opalj/fpcf/" // TODO anti-hardcode - - necessaryFiles.map { filePath => new File(basePath + filePath) } - } + // The fully-qualified name of the class that contains the test methods. + protected def fqTestMethodsClass: String + // The name of the method from which to extract PUVars to analyze. + protected def nameTestMethod: String /** * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. @@ -61,51 +38,56 @@ sealed class StringAnalysisTestRunner( def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation - def determineEntitiesToAnalyze( - project: Project[URL] - ): Iterable[(SEntity, Method)] = { - val entitiesToAnalyze = ListBuffer[(SEntity, Method)]() + def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(SEntity, Method)] = { + var entitiesToAnalyze = Seq[(SEntity, Method)]() val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => - exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) + exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } foreach { m => - StringAnalysisTestRunner.extractUVars( - tacProvider(m), - fqTestMethodsClass, - nameTestMethod - ).foreach { puVar => entitiesToAnalyze.append((puVar, m)) } + entitiesToAnalyze = entitiesToAnalyze ++ extractPUVars(tacProvider(m)).map((_, m)) } entitiesToAnalyze } + /** + * Extracts [[org.opalj.tac.PUVar]]s from a set of statements. The locations of the PUVar are + * identified by the argument to the very first call to [[fqTestMethodsClass]]#[[nameTestMethod]]. + * + * @param tac The tac from which to extract the PUVar, usually derived from the + * method that contains the call(s) to [[fqTestMethodsClass]]#[[nameTestMethod]]. + * @return Returns the arguments of the [[fqTestMethodsClass]]#[[nameTestMethod]] as a PUVars list in the + * order in which they occurred in the given statements. + */ + def extractPUVars(tac: TACode[TACMethodParameter, V]): List[SEntity] = { + tac.cfg.code.instructions.filter { + case VirtualMethodCall(_, declClass, _, name, _, _, _) => + declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod + case _ => false + }.map(_.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)).toList + } + def determineEAS( - entities: Iterable[(SEntity, Method)], - project: Project[URL] + entities: Iterable[(SEntity, Method)], + project: Project[URL] ): Iterable[((SEntity, 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(project).filter(am => m2e.contains(am._1)).flatMap { am => + // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation + methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => m2e(am._1).zipWithIndex.map { - case (duvar, index) => + case (puVar, index) => Tuple3( - (duvar, am._1), + (puVar, am._1), { s: String => s"${am._2(s)} (#$index)" }, List(getStringDefinitionsFromCollection(am._3, index)) ) } } - - eas } } -object StringAnalysisTestRunner { - - private val fqStringDefAnnotation = - "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" +object StringAnalysisTest { /** * Takes an annotation and checks if it is a @@ -115,133 +97,57 @@ object StringAnalysisTestRunner { * @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( - tac: TACode[TACMethodParameter, V], - fqTestMethodsClass: String, - nameTestMethod: String - ): List[SEntity] = { - tac.cfg.code.instructions.filter { - case VirtualMethodCall(_, declClass, _, name, _, _, _) => - declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod - case _ => false - }.map(_.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)).toList - } - + a.annotationType.toJavaClass.getName == "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" } /** * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis]] works correctly with * respect to some well-defined tests. * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class IntraproceduralStringAnalysisTest extends PropertiesTest { - - describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { - val runner = new StringAnalysisTestRunner( - IntraproceduralStringAnalysisTest.fqTestMethodsClass, - IntraproceduralStringAnalysisTest.nameTestMethod, - IntraproceduralStringAnalysisTest.filesToLoad - ) - val p = Project(runner.getRelevantProjectFiles, Array[File]()) - - val manager = p.get(FPCFAnalysesManagerKey) - val ps = p.get(PropertyStoreKey) - val entities = runner.determineEntitiesToAnalyze(p) - val (_, analyses) = manager.runAll( - List(LazyIntraproceduralStringAnalysis), - { _: List[ComputationSpecification[FPCFAnalysis]] => - entities.foreach(ps.force(_, StringConstancyProperty.key)) - } - ) +class IntraproceduralStringAnalysisTest extends StringAnalysisTest { - val testContext = TestContext(p, ps, analyses.map(_._2)) + override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods" + override protected val nameTestMethod = "analyzeString" - val eas = runner.determineEAS(entities, p) + override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/intraprocedural") - ps.shutdown() - validateProperties(testContext, eas, Set("StringConstancy")) - ps.waitOnPhaseCompletion() - } - -} + describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { + val as = executeAnalyses(LazyIntraproceduralStringAnalysis) -object IntraproceduralStringAnalysisTest { + val entities = determineEntitiesToAnalyze(as.project) + entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) - 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[String] = List( - "fixtures/string_analysis/LocalTestMethods.class" - ) + as.propertyStore.shutdown() + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + } } /** * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis]] works correctly with * respect to some well-defined tests. * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class InterproceduralStringAnalysisTest extends PropertiesTest { +class InterproceduralStringAnalysisTest extends StringAnalysisTest { - 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]()) + override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.interprocedural.InterproceduralTestMethods" + override protected val nameTestMethod = "analyzeString" - val entities = runner.determineEntitiesToAnalyze(p) + override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/interprocedural") + override def init(p: Project[URL]): Unit = { p.get(RTACallGraphKey) - val ps = p.get(PropertyStoreKey) - val manager = p.get(FPCFAnalysesManagerKey) - val analysesToRun = Set( - LazyInterproceduralStringAnalysis - ) - - val (_, analyses) = manager.runAll( - analysesToRun, - { _: List[ComputationSpecification[FPCFAnalysis]] => - entities.foreach(ps.force(_, StringConstancyProperty.key)) - } - ) + } - val testContext = TestContext(p, ps, analyses.map(_._2)) - val eas = runner.determineEAS(entities, p) + describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { + val as = executeAnalyses(LazyInterproceduralStringAnalysis) - ps.waitOnPhaseCompletion() - ps.shutdown() + val entities = determineEntitiesToAnalyze(as.project) + entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) - validateProperties(testContext, eas, Set("StringConstancy")) + as.propertyStore.shutdown() + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) } - -} - -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[String] = List( - "fixtures/string_analysis/InterproceduralTestMethods.class", - "fixtures/string_analysis/StringProvider.class", - "fixtures/string_analysis/hierarchies/GreetingService.class", - "fixtures/string_analysis/hierarchies/HelloGreeting.class", - "fixtures/string_analysis/hierarchies/SimpleHelloGreeting.class" - ) } From 28a4c8b531f449615187f6938ac4cd4ca10115ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 14:35:36 +0100 Subject: [PATCH 335/583] Refactor intraprocedural analysis parts --- .../IntraproceduralStringAnalysis.scala | 119 ++++++------------ .../InterpretationHandler.scala | 5 +- .../common/BinaryExprInterpreter.scala | 9 +- .../common/DoubleValueInterpreter.scala | 9 +- .../common/FloatValueInterpreter.scala | 8 +- .../common/IntegerValueInterpreter.scala | 9 +- .../common/NewInterpreter.scala | 9 +- .../common/StringConstInterpreter.scala | 9 +- .../IntraproceduralArrayInterpreter.scala | 60 +++------ .../IntraproceduralFieldInterpreter.scala | 15 +-- .../IntraproceduralGetStaticInterpreter.scala | 14 +-- ...IntraproceduralInterpretationHandler.scala | 36 ++---- ...ralNonVirtualFunctionCallInterpreter.scala | 9 +- ...duralNonVirtualMethodCallInterpreter.scala | 25 +--- ...ceduralStaticFunctionCallInterpreter.scala | 11 +- ...eduralVirtualFunctionCallInterpreter.scala | 82 +++++------- ...oceduralVirtualMethodCallInterpreter.scala | 14 +-- 17 files changed, 144 insertions(+), 299 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 bd670a14aa..622fcdd628 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 @@ -7,7 +7,6 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis @@ -16,20 +15,18 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalP +import org.opalj.fpcf.FinalEP 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.UBP 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 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 @@ -63,9 +60,7 @@ import org.opalj.value.ValueInformation * * @author Patrick Mell */ -class IntraproceduralStringAnalysis( - val project: SomeProject -) extends FPCFAnalysis { +class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalysis { /** * This class is to be used to store state information that are required at a later point in @@ -88,17 +83,15 @@ class IntraproceduralStringAnalysis( var sci = StringConstancyProperty.lb.stringConstancyInformation // Retrieve TAC from 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) // TODO add continuation - } else { - tac = tacaiEOptP.ub.tac.get - } + val tacOpt: Option[TACode[TACMethodParameter, V]] = ps(data._2, TACAI.key) match { + case UBP(tac) => if (tac.tac.isEmpty) None else Some(tac.tac.get) + case _ => None } - val cfg = tac.cfg + // No TAC available, e.g., because the method has no body + if (tacOpt.isEmpty) + return Result(data, StringConstancyProperty.lb) // TODO add continuation + + implicit val tac: TACode[TACMethodParameter, V] = tacOpt.get val stmts = tac.stmts val puVar = data._1 @@ -109,10 +102,9 @@ class IntraproceduralStringAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lb) } - val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) // If not empty, this very routine can only produce an intermediate result - val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() + val dependees: mutable.Map[SContext, ListBuffer[EOptionP[SContext, StringConstancyProperty]]] = 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 @@ -126,11 +118,11 @@ class IntraproceduralStringAnalysis( return Result(data, StringConstancyProperty.lb) } - val paths = pathFinder.findPaths(initDefSites, uVar.definedBy.head) + val paths = new WindowPathFinder(tac.cfg).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, puVar)(tac) + val dependentVars = findDependentVars(leanPaths, stmts, puVar) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar => val toAnalyze = (nextVar, data._2) @@ -138,8 +130,8 @@ class IntraproceduralStringAnalysis( state = ComputationState(leanPaths, dependentVars, fpe2sci, tac) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalP(p) => - return processFinalP(data, dependees.values.flatten, state, ep.e, p) + case finalEP: FinalEP[SContext, StringConstancyProperty] => + return processFinalP(data, dependees.values.flatten, state, finalEP) case _ => if (!dependees.contains(data)) { dependees(data) = ListBuffer() @@ -149,17 +141,14 @@ class IntraproceduralStringAnalysis( } } else { val interpretationHandler = IntraproceduralInterpretationHandler(tac) - sci = new PathTransformer( - interpretationHandler - ).pathToStringTree(leanPaths).reduce(true) + 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(tac) + val interpretationHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => - val r = interHandler.processDefSite(ds).asFinal - r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + interpretationHandler.processDefSite(ds).p.stringConstancyInformation } ) } @@ -178,24 +167,20 @@ class IntraproceduralStringAnalysis( } /** - * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a - * [[FinalP]]. + * Responsible for handling the case that the `propertyStore` outputs a [[FinalEP]]. */ private def processFinalP( - data: SContext, - dependees: Iterable[EOptionP[Entity, Property]], - state: ComputationState, - e: Entity, - p: Property + data: SContext, + dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], + state: ComputationState, + finalEP: FinalEP[SContext, StringConstancyProperty] ): 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[SContext]._1), currentSci) + state.fpe2sci.put(state.var2IndexMapping(finalEP.e._1), finalEP.p.stringConstancyInformation) // No more dependees => Return the result for this analysis run - val remDependees = dependees.filter(_.e != e) - if (remDependees.isEmpty) { + val remainingDependees = dependees.filter(_.e != finalEP.e) + if (remainingDependees.isEmpty) { val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( state.computedLeanPath, @@ -207,8 +192,8 @@ class IntraproceduralStringAnalysis( data, StringConstancyProperty.ub, StringConstancyProperty.lb, - remDependees.toSet, - continuation(data, remDependees, state) + remainingDependees.toSet, + continuation(data, remainingDependees, state) ) } } @@ -223,18 +208,13 @@ class IntraproceduralStringAnalysis( * @return This function can either produce a final result or another intermediate result. */ private def continuation( - data: SContext, - dependees: Iterable[EOptionP[Entity, Property]], - state: ComputationState + data: SContext, + dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], + 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.toSet, - continuation(data, dependees, state) - ) + case finalEP: FinalEP[_, _] => + processFinalP(data, dependees, state, finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]]) + case InterimLUBP(lb, ub) => InterimResult(data, lb, ub, dependees.toSet, continuation(data, dependees, state)) case _ => throw new IllegalStateException("Could not process the continuation successfully.") } @@ -246,11 +226,10 @@ class IntraproceduralStringAnalysis( private def findDependeesAcc( subpath: SubPath, stmts: Array[Stmt[V]], - target: SEntity, - foundDependees: ListBuffer[(SEntity, Int)], - hasTargetBeenSeen: Boolean + target: SEntity )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false + val foundDependees = ListBuffer[(SEntity, Int)]() subpath match { case fpe: FlatPathElement => if (target.toValueOriginForm(tac.pcToIndex).definedBy.contains(fpe.element)) { @@ -265,10 +244,7 @@ class IntraproceduralStringAnalysis( param.definedBy.filter(_ >= 0).foreach { ds => val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append(( - outerExpr.asVirtualFunctionCall.params.head.asVar.toPersistentForm(tac.stmts), - fpe.element - )) + foundDependees.append((param.toPersistentForm(tac.stmts), fpe.element)) } } } @@ -278,14 +254,9 @@ class IntraproceduralStringAnalysis( case npe: NestedPathElement => npe.element.foreach { nextSubpath => if (!encounteredTarget) { - val (_, seen) = findDependeesAcc( - nextSubpath, - stmts, - target, - foundDependees, - encounteredTarget - ) + val (innerFoundDependees, seen) = findDependeesAcc(nextSubpath, stmts, target) encounteredTarget = seen + foundDependees.appendAll(innerFoundDependees) } } (foundDependees, encounteredTarget) @@ -314,13 +285,7 @@ class IntraproceduralStringAnalysis( path.elements.foreach { nextSubpath => if (!wasTargetSeen) { - val (currentDeps, encounteredTarget) = findDependeesAcc( - nextSubpath, - stmts, - ignore, - ListBuffer(), - hasTargetBeenSeen = false - ) + val (currentDeps, encounteredTarget) = findDependeesAcc(nextSubpath, stmts, ignore) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => val newExpressions = InterpretationHandler.findNewOfVar( @@ -335,7 +300,6 @@ class IntraproceduralStringAnalysis( } dependees } - } sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { @@ -362,7 +326,6 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule ps: PropertyStore, analysis: FPCFAnalysis ): Unit = {} - } /** 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 5b44f5d9d9..4223ee4d60 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,12 +22,11 @@ import org.opalj.value.ValueInformation 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 + protected val cfg: CFG[Stmt[DUVar[ValueInformation]], TACStmts[DUVar[ValueInformation]]] = tac.cfg /** * 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]]). + * look-ups (the value is not relevant and thus set to [[Unit]]). */ protected val processedDefSites: mutable.Map[Int, Unit] = mutable.Map() 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 046a27fb07..5e7c4be922 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 @@ -12,8 +12,6 @@ import org.opalj.br.ComputationalTypeInt 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -27,8 +25,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class BinaryExprInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = BinaryExpr[V] @@ -47,7 +45,7 @@ class BinaryExprInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.cTpe match { case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat @@ -55,5 +53,4 @@ class BinaryExprInterpreter( } 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 c67d69e50c..5f4b53e6be 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 @@ -12,8 +12,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -26,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class DoubleValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = DoubleConst @@ -37,7 +35,7 @@ class DoubleValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( @@ -46,5 +44,4 @@ class DoubleValueInterpreter( 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 c5ca41e026..97c568d08c 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 @@ -12,8 +12,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -26,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class FloatValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = FloatConst @@ -37,7 +35,7 @@ class FloatValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( 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 d7ddbf209c..f997693ccb 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 @@ -12,8 +12,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -26,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntegerValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = IntConst @@ -37,7 +35,7 @@ class IntegerValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( @@ -46,5 +44,4 @@ class IntegerValueInterpreter( 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 e2dea6f1c2..f371b1ff4c 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 @@ -9,8 +9,6 @@ package common import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -23,8 +21,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = New @@ -39,7 +37,6 @@ class NewInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, 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 718715f79a..282ebda3e5 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 @@ -12,8 +12,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -27,8 +25,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class StringConstInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StringConst @@ -42,7 +40,7 @@ class StringConstInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( @@ -51,5 +49,4 @@ class StringConstInterpreter( instr.value )) ) - } 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 1da90acc1f..9c8e5dce13 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 @@ -7,13 +7,9 @@ package string_analysis package interpretation package intraprocedural -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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -25,8 +21,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralArrayInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = ArrayLoad[V] @@ -36,50 +32,34 @@ class IntraproceduralArrayInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { 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 { n => - n.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }) - } - // Process ArrayLoads - sortedArrDeclUses.filter { + var scis = Seq.empty[StringConstancyInformation] + + defSites.filter(_ >= 0).sorted.foreach { defSite => + stmts(defSite).asAssignment.targetVar.usedBy.toArray.sorted.foreach { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) => true - case _ => false + // Process ArrayStores + case ArrayStore(_, _, _, value) => + scis = scis ++ value.asVar.definedBy.toArray.sorted.map { + exprHandler.processDefSite(_).p.stringConstancyInformation + } + // Process ArrayLoads + case Assignment(_, _, expr: ArrayLoad[V]) => + scis = scis ++ expr.arrayRef.asVar.definedBy.toArray.sorted.map { + exprHandler.processDefSite(_).p.stringConstancyInformation + } + case _ => } - } foreach { f: Int => - val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - children.appendAll(defs.toArray.sorted.map(exprHandler.processDefSite(_)).map { n => - n.asFinal.p.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) + scis = scis :+ StringConstancyInformation.lb } - FinalEP( - instr, - StringConstancyProperty( - StringConstancyInformation.reduceMultiple( - children.filter(!_.isTheNeutralElement) - ) - ) - ) + FinalEP(instr, StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis))) } - } 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 163c20f28b..dabba7ec48 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 @@ -9,8 +9,6 @@ package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -24,21 +22,18 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralFieldInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = GetField[V] /** - * Fields are not suppoerted by this implementation. Thus, this function always returns a result - * containing [[StringConstancyProperty.lb]]. - * - * @note For this implementation, `defSite` does not play a role. + * Fields are not supported by this implementation. Thus, this function always returns + * [[StringConstancyProperty.lb]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, 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 8fee90d3b1..1aa231829d 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 @@ -9,8 +9,6 @@ package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -24,21 +22,17 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralGetStaticInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + 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.lb]]. - * - * @note For this implementation, `defSite` does not play a role. + * Currently, this type is not interpreted. Thus, this function always returns [[StringConstancyProperty.lb]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) - } 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 0a43050ab0..d1937a1228 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 @@ -10,9 +10,7 @@ package intraprocedural import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.Property 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 @@ -44,10 +42,10 @@ class IntraproceduralInterpretationHandler( override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): EOptionP[Entity, Property] = { + ): FinalEP[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 + val e: Integer = defSite // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { return FinalEP(e, StringConstancyProperty.lb) @@ -56,7 +54,7 @@ class IntraproceduralInterpretationHandler( } processedDefSites(defSite) = () - val result: EOptionP[Entity, Property] = stmts(defSite) match { + stmts(defSite) match { case Assignment(_, _, expr: StringConst) => new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) => @@ -70,40 +68,26 @@ class IntraproceduralInterpretationHandler( case Assignment(_, _, expr: New) => new NewInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - new IntraproceduralVirtualFunctionCallInterpreter( - cfg, - this - ).interpret(expr, defSite) + new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) => new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - new IntraproceduralNonVirtualFunctionCallInterpreter( - cfg, - this - ).interpret(expr, defSite) + new IntraproceduralNonVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: GetField[V]) => new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - new IntraproceduralVirtualFunctionCallInterpreter( - cfg, - this - ).interpret(expr, defSite) + new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case vmc: VirtualMethodCall[V] => new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => - new IntraproceduralNonVirtualMethodCallInterpreter( - cfg, - this - ).interpret(nvmc, defSite) + new IntraproceduralNonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc, defSite) case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } - result } - } object IntraproceduralInterpretationHandler { @@ -111,8 +95,6 @@ object IntraproceduralInterpretationHandler { /** * @see [[IntraproceduralInterpretationHandler]] */ - def apply( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(tac) - + def apply(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]): IntraproceduralInterpretationHandler = + new IntraproceduralInterpretationHandler(tac) } 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 dc521991c3..68f260fafa 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 @@ -9,8 +9,6 @@ package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -22,8 +20,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] @@ -35,7 +33,6 @@ class IntraproceduralNonVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, 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 f64feba46e..a3df8aedab 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 @@ -7,13 +7,9 @@ package string_analysis package interpretation package intraprocedural -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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -26,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] @@ -49,10 +45,7 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret( - instr: T, - defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val prop = instr.name match { case "" => interpretInit(instr) case _ => StringConstancyProperty.getNeutralElement @@ -71,16 +64,10 @@ class IntraproceduralNonVirtualMethodCallInterpreter( init.params.size match { case 0 => StringConstancyProperty.getNeutralElement case _ => - val scis = ListBuffer[StringConstancyInformation]() - init.params.head.asVar.definedBy.foreach { ds => - val r = exprHandler.processDefSite(ds).asFinal - scis.append( - r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - ) + val scis = init.params.head.asVar.definedBy.toList.map { ds => + exprHandler.processDefSite(ds).p.stringConstancyInformation } - val reduced = StringConstancyInformation.reduceMultiple(scis) - StringConstancyProperty(reduced) + StringConstancyProperty(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 9753744822..c87da14f03 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 @@ -9,8 +9,6 @@ package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -24,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] @@ -33,11 +31,8 @@ class IntraproceduralStaticFunctionCallInterpreter( /** * 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): EOptionP[Entity, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int): FinalEP[T, 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 7a06e2f60b..92fe78cf7f 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 @@ -18,8 +18,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -32,8 +30,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -65,15 +63,14 @@ class IntraproceduralVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { 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" => - StringConstancyProperty.lb + case obj: ObjectType if obj.fqn == "java/lang/String" => StringConstancyProperty.lb case FloatType | DoubleType => StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.DYNAMIC, @@ -92,32 +89,26 @@ class IntraproceduralVirtualFunctionCallInterpreter( * 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] - ): StringConstancyProperty = { + private def interpretAppendCall(appendCall: VirtualFunctionCall[V]): 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 val sci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { + // although counter-intuitive, this case may occur if both the receiver and the parameter have been + // processed before 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 (receiverSci.isTheNeutralElement) { + // 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 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) { + } else if (appendSci.isTheNeutralElement) { + // The append value might be empty, if the site has already been processed (then this + // information will come from another StringConstancyInformation object receiverSci - } // Receiver and parameter information are available => Combine them - else { + } else { + // Receiver and parameter information are available => combine them StringConstancyInformation( - StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, - appendSci.constancyLevel - ), + StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.constancyLevel), StringConstancyType.APPEND, receiverSci.possibleStrings + appendSci.possibleStrings ) @@ -129,40 +120,32 @@ class IntraproceduralVirtualFunctionCallInterpreter( /** * This function determines the current value of the receiver object of an `append` call. */ - private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V] - ): StringConstancyProperty = { + 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 val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => val r = exprHandler.processDefSite(ds) - r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + r.asFinal.p.stringConstancyInformation }.filter { sci => !sci.isTheNeutralElement } - val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement - else - scis.head - StringConstancyProperty(sci) + StringConstancyProperty(scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement)) } /** * 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] - ): StringConstancyProperty = { + private def valueOfAppendCall(call: VirtualFunctionCall[V]): StringConstancyProperty = { 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) - var value = r.asFinal.p.asInstanceOf[StringConstancyProperty] + var value = exprHandler.processDefSite(defSiteHead).p // 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( + val r = exprHandler.processDefSite( cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min - ).asFinal - value = r.asFinal.p.asInstanceOf[StringConstancyProperty] + ) + value = r.p } val sci = value.stringConstancyInformation @@ -174,9 +157,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( if (call.descriptor.parameterType(0).isCharType && sci.constancyLevel == StringConstancyLevel.CONSTANT ) { - sci.copy( - possibleStrings = sci.possibleStrings.toInt.toChar.toString - ) + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) } else { sci } @@ -199,11 +180,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( * 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] - ): StringConstancyProperty = { - val finalEP = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asFinal - finalEP.p.asInstanceOf[StringConstancyProperty] + private def interpretToStringCall(call: VirtualFunctionCall[V]): StringConstancyProperty = { + exprHandler.processDefSite(call.receiver.asVar.definedBy.head).p } /** @@ -211,8 +189,6 @@ class IntraproceduralVirtualFunctionCallInterpreter( * (Currently, this function simply approximates `replace` functions by returning the lower * bound of [[StringConstancyProperty]]). */ - private def interpretReplaceCall( - instr: VirtualFunctionCall[V] - ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace - + private def interpretReplaceCall(instr: VirtualFunctionCall[V]): StringConstancyProperty = + InterpretationHandler.getStringConstancyPropertyForReplace } 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 afdb008523..50f65dfefb 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 @@ -12,8 +12,6 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -26,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualMethodCall[V] @@ -50,15 +48,11 @@ class IntraproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { - case "setLength" => StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.RESET - ) + case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } - } From 51121db9b4efc9513fa95090eafa4a54071b4fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 16:53:40 +0100 Subject: [PATCH 336/583] Refactor interprocedural analysis parts --- .../AbstractStringInterpreter.scala | 25 +++--- .../InterpretationHandler.scala | 26 ++---- .../ArrayPreparationInterpreter.scala | 51 ++++------- .../InterproceduralFieldInterpreter.scala | 10 +-- ...InterproceduralInterpretationHandler.scala | 88 ++++++------------- ...ralNonVirtualFunctionCallInterpreter.scala | 16 ++-- ...duralNonVirtualMethodCallInterpreter.scala | 32 +++---- ...ceduralStaticFunctionCallInterpreter.scala | 36 +++----- ...oceduralVirtualMethodCallInterpreter.scala | 16 ++-- .../interprocedural/NewArrayPreparer.scala | 13 ++- ...alFunctionCallPreparationInterpreter.scala | 84 +++++++----------- .../finalizer/AbstractFinalizer.scala | 1 - .../finalizer/ArrayLoadFinalizer.scala | 17 ++-- .../finalizer/GetFieldFinalizer.scala | 13 +-- .../finalizer/NewArrayFinalizer.scala | 6 +- .../NonVirtualMethodCallFinalizer.scala | 14 +-- .../StaticFunctionCallFinalizer.scala | 14 +-- .../VirtualFunctionCallFinalizer.scala | 22 ++--- 18 files changed, 161 insertions(+), 323 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 bc583cba61..451e0b07ff 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,7 +8,6 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.cfg.CFG @@ -18,7 +17,7 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.Property +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler @@ -38,8 +37,8 @@ import org.opalj.value.ValueInformation * @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 @@ -175,16 +174,13 @@ abstract class AbstractStringInterpreter( * all results in the inner-most sequence are final! */ protected def convertEvaluatedParameters( - evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, Property]]]] - ): ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer.from(evaluatedParameters.map { paramList => - ListBuffer.from(paramList.map { param => - StringConstancyInformation.reduceMultiple( - param.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - } - ) + evaluatedParameters: Seq[Seq[Seq[FinalEP[Entity, StringConstancyProperty]]]] + ): ListBuffer[ListBuffer[StringConstancyInformation]] = + ListBuffer.from(evaluatedParameters.map { paramList => + ListBuffer.from(paramList.map { param => + StringConstancyInformation.reduceMultiple(param.map { _.p.stringConstancyInformation }) + }) }) - }) /** * @param instr The instruction that is to be interpreted. It is the responsibility of @@ -203,6 +199,5 @@ 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): EOptionP[Entity, Property] - + def interpret(instr: T, defSite: Int): EOptionP[Entity, 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 4223ee4d60..f3b34d6854 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 @@ -6,9 +6,10 @@ package analyses package string_analysis package interpretation +import org.opalj.br.ObjectType + 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 @@ -16,7 +17,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.Property import org.opalj.value.ValueInformation abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { @@ -54,7 +54,7 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): EOptionP[Entity, Property] + ): EOptionP[Entity, StringConstancyProperty] /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As @@ -67,7 +67,6 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value def reset(): Unit = { processedDefSites.clear() } - } object InterpretationHandler { @@ -75,34 +74,24 @@ object InterpretationHandler { /** * 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 `toString` of [[StringBuilder]] or - * [[StringBuffer]]. */ def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { case VirtualFunctionCall(_, clazz, _, "toString", _, _, _) => - val className = clazz.mostPreciseObjectType.fqn - (className == "java/lang/StringBuilder" || className == "java/lang/StringBuffer") + clazz.mostPreciseObjectType == ObjectType.StringBuilder || + clazz.mostPreciseObjectType == ObjectType.StringBuffer 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 = if (expr.isStringConst) { true } else { if (expr.isVar) { val value = expr.asVar.value - value.isReferenceValue && value.asReferenceValue.upperTypeBound.exists { - _.toJava == "java.lang.String" - } + value.isReferenceValue && value.asReferenceValue.upperTypeBound.exists { _.toJava == "java.lang.String" } } else { false } @@ -144,7 +133,7 @@ object InterpretationHandler { ): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder if (toString.name != "toString") { - return List() + return List.empty } val defSites = ListBuffer[Int]() @@ -276,5 +265,4 @@ object InterpretationHandler { StringConstancyType.REPLACE, StringConstancyInformation.UnknownWordSymbol )) - } 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 b44f32ad12..8803f9f820 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 @@ -8,10 +8,10 @@ package interpretation package interprocedural 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.collection.immutable.IntTrieSet import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP @@ -30,10 +30,10 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class ArrayPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = ArrayLoad[V] @@ -51,12 +51,7 @@ class ArrayPreparationInterpreter( 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( - instr, - state.tac.stmts - ) - + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, ep) => if (ep.isFinal) @@ -65,16 +60,14 @@ class ArrayPreparationInterpreter( } // Add information of parameters - defSites.filter(_ < 0).foreach { ds => + instr.arrayRef.asVar.definedBy.toArray.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) } // 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) + // (to either indicate that further computation are necessary or a final result is already present) val interims = results.find(!_.isFinal) if (interims.isDefined) { interims.get @@ -115,29 +108,19 @@ object ArrayPreparationInterpreter { * given instruction. The result list is sorted in ascending order. */ def getStoreAndLoadDefSites(instr: T, stmts: Array[Stmt[V]]): List[Int] = { - 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 { + var defSites = IntTrieSet.empty + instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => + stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) => true - case _ => false + case ArrayStore(_, _, _, value) => + defSites = defSites ++ value.asVar.definedBy.toArray + case Assignment(_, _, expr: ArrayLoad[V]) => + defSites = defSites ++ expr.asArrayLoad.arrayRef.asVar.definedBy.toArray + case _ => } - } foreach { f: Int => - val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - allDefSites.appendAll(defs.toArray) } } - allDefSites.sorted.toList + defSites.toList.sorted } - } 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 7d2e587498..504dbca159 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 @@ -86,7 +86,7 @@ class InterproceduralFieldInterpreter( EPK(state.entity, StringConstancyProperty.key) } else { tac match { - case Some(methodTac) => + case Some(_) => val entity = (PUVar(parameter.get._1, parameter.get._2), method) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { @@ -110,15 +110,11 @@ class InterproceduralFieldInterpreter( 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 + possibleStrings = "(^null$|" + StringConstancyInformation.UnknownWordSymbol + ")" ) + state.appendToFpe2Sci(defSitEntity, StringConstancyInformation.lb) FinalEP(defSitEntity, StringConstancyProperty(sci)) } else { // If all results are final, determine all possible values for the field. Otherwise, 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 bf421318f6..08d47ddbd2 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 @@ -92,24 +92,20 @@ class InterproceduralInterpretationHandler( 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: DoubleConst) => processConstExpr(expr, defSite) // TODO what about long consts 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) + case Assignment(_, _, expr: New) => processNew(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]) => - processStaticFunctionCall(expr, defSite, params) - case ExprStmt(_, expr: StaticFunctionCall[V]) => - processStaticFunctionCall(expr, defSite, params) + case ExprStmt(_, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite, params) + case Assignment(_, _, expr: StaticFunctionCall[V]) => 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: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) - case vmc: VirtualMethodCall[V] => - processVirtualMethodCall(vmc, defSite, callees) + case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) @@ -129,12 +125,10 @@ class InterproceduralInterpretationHandler( 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 - ) + case sc: StringConst => new StringConstInterpreter(cfg, this).interpret(sc, defSite) + case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } - val sci = finalEP.asFinal.p.stringConstancyInformation + val sci = finalEP.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) state.appendToInterimFpe2Sci(defSite, sci) processedDefSites.remove(defSite) @@ -156,7 +150,7 @@ class InterproceduralInterpretationHandler( params ).interpret(expr, defSite) val sci = if (r.isFinal) { - r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + r.asFinal.p.stringConstancyInformation } else { processedDefSites.remove(defSite) StringConstancyInformation.lb @@ -180,7 +174,7 @@ class InterproceduralInterpretationHandler( params ).interpret(expr, defSite) val sci = if (r.isFinal) { - r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + r.asFinal.p.stringConstancyInformation } else { processedDefSites.remove(defSite) StringConstancyInformation.lb @@ -255,9 +249,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: StaticFunctionCall[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( cfg, @@ -279,10 +273,7 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[BinaryExpr]]s. */ - private def processBinaryExpr( - expr: BinaryExpr[V], - defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + 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) @@ -295,12 +286,8 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[GetField]]s. */ - private def processGetField( - expr: FieldRead[V], - defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralFieldInterpreter( - state, + private def processGetField(expr: FieldRead[V], defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + val r = new InterproceduralFieldInterpreter(state, this, ps, fieldAccessInformation, @@ -357,16 +344,11 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualMethodCall( - nvmc: NonVirtualMethodCall[V], - defSite: Int + nvmc: NonVirtualMethodCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralNonVirtualMethodCallInterpreter( - cfg, - this, - ps, - state, - declaredMethods - ).interpret(nvmc, defSite) + val r = new InterproceduralNonVirtualMethodCallInterpreter(cfg, this, ps, state, declaredMethods) + .interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => state.appendToInterimFpe2Sci(defSite, p.stringConstancyInformation) @@ -383,10 +365,7 @@ class InterproceduralInterpretationHandler( * function handles the steps necessary to provide information for computing intermediate * results. */ - private def doInterimResultHandling( - result: EOptionP[Entity, Property], - defSite: Int - ): Unit = { + private def doInterimResultHandling(result: EOptionP[Entity, Property], defSite: Int): Unit = { val sci = if (result.isFinal) { result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } else { @@ -399,10 +378,7 @@ 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 = { + private def getParam(params: Seq[Seq[StringConstancyInformation]], defSite: Int): StringConstancyInformation = { val paramPos = Math.abs(defSite + 2) if (params.exists(_.length <= paramPos)) { StringConstancyInformation.lb @@ -415,10 +391,7 @@ class InterproceduralInterpretationHandler( /** * Finalized a given definition state. */ - def finalizeDefSite( - defSite: Int, - state: InterproceduralComputationState - ): Unit = { + def finalizeDefSite(defSite: Int, state: InterproceduralComputationState): Unit = { if (defSite < 0) { state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) } else { @@ -441,11 +414,8 @@ class InterproceduralInterpretationHandler( StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case ExprStmt(_, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) - case _ => state.appendToFpe2Sci( - defSite, - StringConstancyProperty.lb.stringConstancyInformation, - reset = true - ) + case _ => + state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) } } } 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 d82127e48a..708fc09b88 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 @@ -15,7 +15,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** @@ -27,12 +26,12 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] @@ -77,12 +76,11 @@ class InterproceduralNonVirtualFunctionCallInterpreter( } eps } - results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + results.find(_.isRefinable).getOrElse(results.head) } } else { state.appendToMethodPrep2defSite(m, defSite) 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 169112cfad..6469e1e43f 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.fpcf.PropertyStore * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] @@ -50,10 +50,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret( - instr: T, - defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { case "" => interpretInit(instr, e) @@ -68,10 +65,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit( - init: T, - defSite: Integer - ): EOptionP[Entity, StringConstancyProperty] = { + private def interpretInit(init: T, defSite: Integer): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { case 0 => FinalEP(defSite, StringConstancyProperty.getNeutralElement) case _ => @@ -79,23 +73,18 @@ class InterproceduralNonVirtualMethodCallInterpreter( (ds, exprHandler.processDefSite(ds, List())) } if (results.forall(_._2.isFinal)) { - // Final result is available val reduced = StringConstancyInformation.reduceMultiple(results.map { r => r._2.asFinal.p.stringConstancyInformation }) FinalEP(defSite, StringConstancyProperty(reduced)) } else { - // Some intermediate results => register necessary information from final - // results and return an intermediate result + // Some intermediate results => register necessary information from final results and return an + // intermediate result val returnIR = results.find(r => !r._2.isFinal).get._2 results.foreach { case (ds, r) => if (r.isFinal) { - state.appendToFpe2Sci( - ds, - r.asFinal.p.stringConstancyInformation, - reset = true - ) + state.appendToFpe2Sci(ds, r.asFinal.p.stringConstancyInformation, reset = true) } case _ => } @@ -103,5 +92,4 @@ class InterproceduralNonVirtualMethodCallInterpreter( } } } - } 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 e74a36340e..2dbf9a4178 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 @@ -69,12 +69,8 @@ class InterproceduralStaticFunctionCallInterpreter( * 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] - ): EOptionP[Entity, StringConstancyProperty] = { - val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds => - exprHandler.processDefSite(ds, params) - } + private def processStringValueOf(call: StaticFunctionCall[V]): EOptionP[Entity, StringConstancyProperty] = { + val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_, params) } val interim = results.find(_.isRefinable) if (interim.isDefined) { interim.get @@ -92,8 +88,7 @@ class InterproceduralStaticFunctionCallInterpreter( } else { scis } - val finalSci = StringConstancyInformation.reduceMultiple(finalScis) - FinalEP(call, StringConstancyProperty(finalSci)) + FinalEP(call, StringConstancyProperty(StringConstancyInformation.reduceMultiple(finalScis))) } } @@ -101,20 +96,16 @@ class InterproceduralStaticFunctionCallInterpreter( * This function interprets an arbitrary static function call. */ private def processArbitraryCall( - instr: StaticFunctionCall[V], - defSite: Int + instr: StaticFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC( - instr.pc, - ps, - state.callees, - typeIterator - ) + val methods, _ = getMethodsForPC(instr.pc, ps, state.callees, typeIterator) - // Static methods cannot be overwritten, thus 1) we do not need the second return value of - // getMethodsForPC and 2) interpreting the head is enough + // 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) + state.appendToFpe2Sci(defSite, StringConstancyInformation.lb) return FinalEP(instr, StringConstancyProperty.lb) } @@ -162,14 +153,14 @@ class InterproceduralStaticFunctionCallInterpreter( state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar 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 + // A function without returns, e.g., because it is guaranteed to throw an exception, is approximated + // with the lower bound FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => @@ -191,5 +182,4 @@ class InterproceduralStaticFunctionCallInterpreter( 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 862c9d7af0..1fbd0b96c0 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 @@ -13,8 +13,6 @@ import org.opalj.br.fpcf.properties.cg.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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -27,9 +25,9 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class InterproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualMethodCall[V] @@ -50,15 +48,11 @@ class InterproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { - case "setLength" => StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.RESET - ) + case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } - } 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 8d861526ba..b68ff04e27 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 @@ -15,7 +15,7 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** - * The `NewArrayPreparer` is responsible for preparing [[NewArray]] expressions. + * 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 @@ -27,10 +27,10 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewArrayPreparer( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NewArray[V] @@ -48,7 +48,7 @@ class NewArrayPreparer( 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) + return FinalEP(instr, StringConstancyProperty.lb) } // Get all sites that define array values and process them @@ -97,5 +97,4 @@ class NewArrayPreparer( 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 41e70570b4..bbf5e155f3 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 @@ -21,8 +21,8 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** @@ -35,13 +35,13 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class VirtualFunctionCallPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]], - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + params: List[Seq[StringConstancyInformation]], + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -91,23 +91,18 @@ class VirtualFunctionCallPreparationInterpreter( } 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) + state.appendToFpe2Sci(defSite, result.asFinal.p.stringConstancyInformation) } 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 + * [[FinalEP]] 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 - ): EOptionP[Entity, StringConstancyProperty] = { + private def interpretArbitraryCall(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val (methods, _) = getMethodsForPC( instr.pc, ps, @@ -120,8 +115,7 @@ class VirtualFunctionCallPreparationInterpreter( } // TODO: Type Iterator! val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) - val instrClassName = - instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava + val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava val relevantPCs = directCallSites.filter { case (_, calledMethods) => calledMethods.exists { m => @@ -153,7 +147,7 @@ class VirtualFunctionCallPreparationInterpreter( state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) val results = methods.map { nextMethod => val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { @@ -186,8 +180,8 @@ class VirtualFunctionCallPreparationInterpreter( } } - val finalResults = results.filter(_.isInstanceOf[Result]) - val intermediateResults = results.filter(!_.isInstanceOf[Result]) + val finalResults = results.filter(_.isFinal) + val intermediateResults = results.filter(_.isRefinable) if (results.length == finalResults.length) { finalResults.head } else { @@ -215,12 +209,8 @@ class VirtualFunctionCallPreparationInterpreter( return appendResult } - val receiverScis = receiverResults.map { r => - val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] - p.stringConstancyInformation - } - val appendSci = - appendResult.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + val receiverScis = receiverResults.map { _.asFinal.p.stringConstancyInformation } + val appendSci = appendResult.asFinal.p.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 @@ -257,15 +247,15 @@ class VirtualFunctionCallPreparationInterpreter( * 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]] + * not be computed. Otherwise, the result list will contain >= 1 elements of type [[FinalEP]] * 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], - state: InterproceduralComputationState + call: VirtualFunctionCall[V], + state: InterproceduralComputationState ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -278,12 +268,9 @@ class VirtualFunctionCallPreparationInterpreter( } 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) + // Extend the state by the final results not being the neutral elements (they might need to be finalized later) finalResultsWithoutNeutralElements.foreach { next => - val p = next._2.asFinal.p.asInstanceOf[StringConstancyProperty] - val sci = p.stringConstancyInformation - state.appendToFpe2Sci(next._1, sci) + state.appendToFpe2Sci(next._1, next._2.asFinal.p.stringConstancyInformation) } if (intermediateResults.isEmpty) { @@ -298,8 +285,8 @@ class VirtualFunctionCallPreparationInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], - state: InterproceduralComputationState + call: VirtualFunctionCall[V], + state: InterproceduralComputationState ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar @@ -311,9 +298,7 @@ class VirtualFunctionCallPreparationInterpreter( return values.find(_.isRefinable).get } - val sciValues = values.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - } + val sciValues = values.map { _.asFinal.p.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 @@ -325,12 +310,10 @@ class VirtualFunctionCallPreparationInterpreter( } else { 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 + r match { + case FinalP(p) => newValueSci = p.stringConstancyInformation + // Defer the computation if there is no final result yet + case _ => return r } } } else { @@ -377,9 +360,7 @@ class VirtualFunctionCallPreparationInterpreter( * 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] - ): EOptionP[Entity, StringConstancyProperty] = + private def interpretToStringCall(call: VirtualFunctionCall[V]): EOptionP[Entity, StringConstancyProperty] = // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) @@ -388,14 +369,11 @@ class VirtualFunctionCallPreparationInterpreter( * (Currently, this function simply approximates `replace` functions by returning the lower * bound of [[StringConstancyProperty]]). */ - private def interpretReplaceCall( - instr: VirtualFunctionCall[V] - ): EOptionP[Entity, StringConstancyProperty] = + private def interpretReplaceCall(instr: VirtualFunctionCall[V]): 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/interpretation/interprocedural/finalizer/AbstractFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala index 9c19bc2199..f5d9820452 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 @@ -36,5 +36,4 @@ abstract class AbstractFinalizer(state: InterproceduralComputationState) { * @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/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index a4e2b9260d..97b81e6b08 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -17,8 +17,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * @author Patrick Mell */ class ArrayLoadFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = ArrayLoad[V] @@ -29,10 +29,7 @@ class ArrayLoadFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( - instr, - state.tac.stmts - ) + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) allDefSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { @@ -44,14 +41,10 @@ class ArrayLoadFinalizer( allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds => state.fpe2sci(ds) } )) } - } object ArrayLoadFinalizer { - def apply( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] - ): ArrayLoadFinalizer = new ArrayLoadFinalizer(state, cfg) - + def apply(state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]]): ArrayLoadFinalizer = + new ArrayLoadFinalizer(state, cfg) } 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 286b0a277c..a7c17b885e 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 @@ -8,9 +8,7 @@ package interpretation package interprocedural package finalizer -class GetFieldFinalizer( - state: InterproceduralComputationState -) extends AbstractFinalizer(state) { +class GetFieldFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { override protected type T = FieldRead[V] @@ -21,16 +19,11 @@ class GetFieldFinalizer( */ 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 + // called after all dependencies are resolved. state.iHandler.processDefSite(defSite) - } object GetFieldFinalizer { - def apply( - state: InterproceduralComputationState - ): GetFieldFinalizer = new GetFieldFinalizer(state) - + def apply(state: InterproceduralComputationState): GetFieldFinalizer = new GetFieldFinalizer(state) } 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 index 1f618fe2e1..517ff633aa 100644 --- 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 @@ -14,8 +14,8 @@ import org.opalj.br.cfg.CFG * @author Patrick Mell */ class NewArrayFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = NewArray[V] @@ -28,7 +28,6 @@ class NewArrayFinalizer( override def finalizeInterpretation(instr: T, defSite: Int): Unit = // Simply re-trigger the computation state.iHandler.processDefSite(defSite) - } object NewArrayFinalizer { @@ -37,5 +36,4 @@ object NewArrayFinalizer { state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] ): NewArrayFinalizer = new NewArrayFinalizer(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 a37d789916..f285cdceda 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 @@ -8,15 +8,12 @@ package interpretation package interprocedural package finalizer -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** * @author Patrick Mell */ -class NonVirtualMethodCallFinalizer( - state: InterproceduralComputationState -) extends AbstractFinalizer(state) { +class NonVirtualMethodCallFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { override type T = NonVirtualMethodCall[V] @@ -35,17 +32,14 @@ class NonVirtualMethodCallFinalizer( val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } StringConstancyInformation.reduceMultiple(scis.flatten.toList) } else { - StringConstancyProperty.lb.stringConstancyInformation + StringConstancyInformation.lb } state.appendToFpe2Sci(defSite, toAppend, reset = true) } - } object NonVirtualMethodCallFinalizer { - def apply( - state: InterproceduralComputationState - ): NonVirtualMethodCallFinalizer = new NonVirtualMethodCallFinalizer(state) - + def apply(state: InterproceduralComputationState): NonVirtualMethodCallFinalizer = + new NonVirtualMethodCallFinalizer(state) } 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 index 5ea0b2cfcc..8c1c611ab4 100644 --- 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 @@ -8,15 +8,12 @@ package interpretation package interprocedural package finalizer -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** * @author Patrick Mell */ -class StaticFunctionCallFinalizer( - state: InterproceduralComputationState -) extends AbstractFinalizer(state) { +class StaticFunctionCallFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { override type T = StaticFunctionCall[V] @@ -41,17 +38,14 @@ class StaticFunctionCallFinalizer( val scis = defSites.map { state.fpe2sci } StringConstancyInformation.reduceMultiple(scis.flatten.toList) } else { - StringConstancyProperty.lb.stringConstancyInformation + StringConstancyInformation.lb } state.appendToFpe2Sci(defSite, toAppend, reset = true) } - } object StaticFunctionCallFinalizer { - def apply( - state: InterproceduralComputationState - ): StaticFunctionCallFinalizer = new StaticFunctionCallFinalizer(state) - + def apply(state: InterproceduralComputationState): StaticFunctionCallFinalizer = + new StaticFunctionCallFinalizer(state) } 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 09c4fa0e8d..3600043a34 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 @@ -9,7 +9,6 @@ package interprocedural package finalizer 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 @@ -18,8 +17,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType * @author Patrick Mell */ class VirtualFunctionCallFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = VirtualFunctionCall[V] @@ -34,11 +33,7 @@ class VirtualFunctionCallFinalizer( instr.name match { case "append" => finalizeAppend(instr, defSite) case "toString" => finalizeToString(instr, defSite) - case _ => state.appendToFpe2Sci( - defSite, - StringConstancyProperty.lb.stringConstancyInformation, - reset = true - ) + case _ => state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) } } @@ -72,9 +67,7 @@ class VirtualFunctionCallFinalizer( } } val appendSci = if (paramDefSites.forall(state.fpe2sci.contains)) { - StringConstancyInformation.reduceMultiple( - paramDefSites.flatMap(state.fpe2sci(_)) - ) + StringConstancyInformation.reduceMultiple(paramDefSites.flatMap(state.fpe2sci(_))) } else StringConstancyInformation.lb val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { @@ -85,10 +78,7 @@ class VirtualFunctionCallFinalizer( receiverSci } else { StringConstancyInformation( - StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, - appendSci.constancyLevel - ), + StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.constancyLevel), StringConstancyType.APPEND, receiverSci.possibleStrings + appendSci.possibleStrings ) @@ -114,7 +104,6 @@ class VirtualFunctionCallFinalizer( } state.appendToFpe2Sci(defSite, finalSci) } - } object VirtualFunctionCallFinalizer { @@ -123,5 +112,4 @@ object VirtualFunctionCallFinalizer { state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) - } From 0b766e1f561eac48ffa0948a78c7aa5a9f708574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 22:09:08 +0100 Subject: [PATCH 337/583] Fix formatting --- .../info/StringAnalysisReflectiveCalls.scala | 18 +++-- .../src/main/scala/org/opalj/tac/PDUVar.scala | 6 +- .../InterproceduralStringAnalysis.scala | 27 +++---- .../IntraproceduralStringAnalysis.scala | 33 +++++---- .../AbstractStringInterpreter.scala | 15 ++-- .../InterpretationHandler.scala | 4 +- .../common/BinaryExprInterpreter.scala | 4 +- .../common/DoubleValueInterpreter.scala | 4 +- .../common/FloatValueInterpreter.scala | 4 +- .../common/IntegerValueInterpreter.scala | 4 +- .../common/NewInterpreter.scala | 4 +- .../common/StringConstInterpreter.scala | 4 +- .../ArrayPreparationInterpreter.scala | 9 ++- ...InterproceduralInterpretationHandler.scala | 73 ++++++++++--------- ...ralNonVirtualFunctionCallInterpreter.scala | 12 +-- ...duralNonVirtualMethodCallInterpreter.scala | 12 +-- ...ceduralStaticFunctionCallInterpreter.scala | 14 ++-- ...oceduralVirtualMethodCallInterpreter.scala | 8 +- .../interprocedural/NewArrayPreparer.scala | 8 +- ...alFunctionCallPreparationInterpreter.scala | 21 +++--- .../finalizer/ArrayLoadFinalizer.scala | 4 +- .../finalizer/NewArrayFinalizer.scala | 4 +- .../VirtualFunctionCallFinalizer.scala | 6 +- .../IntraproceduralArrayInterpreter.scala | 4 +- .../IntraproceduralFieldInterpreter.scala | 4 +- .../IntraproceduralGetStaticInterpreter.scala | 4 +- ...ralNonVirtualFunctionCallInterpreter.scala | 4 +- ...duralNonVirtualMethodCallInterpreter.scala | 4 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...eduralVirtualFunctionCallInterpreter.scala | 4 +- ...oceduralVirtualMethodCallInterpreter.scala | 6 +- .../preprocessing/AbstractPathFinder.scala | 6 +- 32 files changed, 173 insertions(+), 165 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 f4a2539071..52ba6c045b 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 @@ -4,9 +4,11 @@ package support package info import scala.annotation.switch + import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.Method import org.opalj.br.ReferenceType import org.opalj.br.analyses.BasicReport @@ -178,10 +180,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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 + ps: PropertyStore, + method: Method, + call: Call[V], + resultMap: ResultMapType )(implicit stmts: Array[Stmt[V]]): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) @@ -232,10 +234,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } private def processStatements( - ps: PropertyStore, - tac: TACode[TACMethodParameter, V], - m: Method, - resultMap: ResultMapType + ps: PropertyStore, + tac: TACode[TACMethodParameter, V], + m: Method, + resultMap: ResultMapType ): Unit = { implicit val stmts: Array[Stmt[V]] = tac.stmts stmts.foreach { stmt => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala index e9f5465b12..efd60520a3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala @@ -36,7 +36,7 @@ abstract class PDUVar[+Value <: ValueInformation] { } class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( - val value: Value, + val value: Value, val defPCs: PCs ) extends PDUVar[Value] { @@ -61,8 +61,8 @@ class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ object PUVar { def apply(d: org.opalj.ai.ValuesDomain)( - value: d.DomainValue, - defPCs: PCs + value: d.DomainValue, + defPCs: PCs ): PUVar[d.DomainValue] = { new PUVar[d.DomainValue](value, defPCs) } 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 6a6fb64061..4ec027c0a0 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 @@ -616,8 +616,8 @@ class InterproceduralStringAnalysis( * [[computeLeanPathForStringBuilder]]. */ private def computeLeanPath( - value: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + value: V, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): Path = { val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { @@ -661,8 +661,8 @@ class InterproceduralStringAnalysis( * `(null, false)` and otherwise `(computed lean path, true)`. */ private def computeLeanPathForStringBuilder( - value: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + value: V, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ): (Path, Boolean) = { val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) @@ -701,11 +701,11 @@ class InterproceduralStringAnalysis( * FlatPathElement.element in which it occurs. */ private def findDependeesAcc( - subpath: SubPath, - stmts: Array[Stmt[V]], - target: SEntity, - foundDependees: ListBuffer[(SEntity, Int)], - hasTargetBeenSeen: Boolean + subpath: SubPath, + stmts: Array[Stmt[V]], + target: SEntity, + foundDependees: ListBuffer[(SEntity, Int)], + hasTargetBeenSeen: Boolean )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false subpath match { @@ -761,9 +761,9 @@ class InterproceduralStringAnalysis( * this variable as `ignore`. */ private def findDependentVars( - path: Path, - stmts: Array[Stmt[V]], - ignore: SEntity + path: Path, + stmts: Array[Stmt[V]], + ignore: SEntity )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), stmts) @@ -780,7 +780,8 @@ class InterproceduralStringAnalysis( ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair => - val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1.toValueOriginForm(tac.pcToIndex), stmts) + val newExpressions = + InterpretationHandler.findNewOfVar(nextPair._1.toValueOriginForm(tac.pcToIndex), stmts) if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) } 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 622fcdd628..6a4cf8dece 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 @@ -7,6 +7,7 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis @@ -68,14 +69,14 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys * 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[SEntity, 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[SEntity, 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: SContext): ProperPropertyComputationResult = { @@ -85,7 +86,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys // Retrieve TAC from property store val tacOpt: Option[TACode[TACMethodParameter, V]] = ps(data._2, TACAI.key) match { case UBP(tac) => if (tac.tac.isEmpty) None else Some(tac.tac.get) - case _ => None + case _ => None } // No TAC available, e.g., because the method has no body if (tacOpt.isEmpty) @@ -215,7 +216,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys case finalEP: FinalEP[_, _] => processFinalP(data, dependees, state, finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]]) case InterimLUBP(lb, ub) => InterimResult(data, lb, ub, dependees.toSet, continuation(data, dependees, state)) - case _ => throw new IllegalStateException("Could not process the continuation successfully.") + case _ => throw new IllegalStateException("Could not process the continuation successfully.") } /** @@ -224,9 +225,9 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys * [[FlatPathElement.element]] in which it occurs. */ private def findDependeesAcc( - subpath: SubPath, - stmts: Array[Stmt[V]], - target: SEntity + subpath: SubPath, + stmts: Array[Stmt[V]], + target: SEntity )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { var encounteredTarget = false val foundDependees = ListBuffer[(SEntity, Int)]() @@ -275,9 +276,9 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys * this variable as `ignore`. */ private def findDependentVars( - path: Path, - stmts: Array[Stmt[V]], - ignore: SEntity + path: Path, + stmts: Array[Stmt[V]], + ignore: SEntity )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), stmts) 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 451e0b07ff..bf22165835 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 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.cfg.CFG @@ -37,8 +38,8 @@ import org.opalj.value.ValueInformation * @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 @@ -129,11 +130,11 @@ abstract class AbstractStringInterpreter( * entities to functions, `entity2function`. */ protected def evaluateParameters( - params: List[Seq[Expr[V]]], - iHandler: InterproceduralInterpretationHandler, - funCall: FunctionCall[V], - functionArgsPos: NonFinalFunctionArgsPos, - entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] + params: List[Seq[Expr[V]]], + iHandler: InterproceduralInterpretationHandler, + funCall: FunctionCall[V], + functionArgsPos: NonFinalFunctionArgsPos, + entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] ): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { 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 f3b34d6854..97341fbacd 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 @@ -6,10 +6,10 @@ package analyses package string_analysis package interpretation -import org.opalj.br.ObjectType - import scala.collection.mutable import scala.collection.mutable.ListBuffer + +import org.opalj.br.ObjectType import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty 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/common/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala index 5e7c4be922..4589abc398 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 @@ -25,8 +25,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class BinaryExprInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = BinaryExpr[V] 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 5f4b53e6be..1e0c9b8797 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 @@ -24,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class DoubleValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = DoubleConst 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 97c568d08c..4c0c7b7246 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 @@ -24,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class FloatValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = FloatConst 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 f997693ccb..23250a6984 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 @@ -24,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntegerValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = IntConst 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 f371b1ff4c..6a0a589235 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 @@ -21,8 +21,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = New 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 282ebda3e5..fad5a16ef3 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 @@ -25,8 +25,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class StringConstInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StringConst 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 8803f9f820..d26047b84d 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 @@ -8,6 +8,7 @@ package interpretation package interprocedural 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 @@ -30,10 +31,10 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class ArrayPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) 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/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 08d47ddbd2..09b6ab254b 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 @@ -89,24 +89,24 @@ class InterproceduralInterpretationHandler( val callees = state.callees stmts(defSite) match { - 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) // TODO what about long consts - 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) - 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, params) - case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite, params) - case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) + 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) // TODO what about long consts + 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) + 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, 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) - case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) - case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) + case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) + case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) + case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) @@ -126,7 +126,7 @@ class InterproceduralInterpretationHandler( case fc: FloatConst => new FloatValueInterpreter(cfg, this).interpret(fc, defSite) case dc: DoubleConst => new DoubleValueInterpreter(cfg, this).interpret(dc, defSite) case sc: StringConst => new StringConstInterpreter(cfg, this).interpret(sc, defSite) - case c => throw new IllegalArgumentException(s"Unsupported const value: $c") + case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } val sci = finalEP.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) @@ -139,9 +139,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[ArrayLoad]]s. */ private def processArrayLoad( - expr: ArrayLoad[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: ArrayLoad[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new ArrayPreparationInterpreter( cfg, @@ -163,9 +163,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NewArray]]s. */ private def processNewArray( - expr: NewArray[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: NewArray[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new NewArrayPreparer( cfg, @@ -201,9 +201,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ private def processVFC( - expr: VirtualFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: VirtualFunctionCall[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( cfg, @@ -249,9 +249,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + expr: StaticFunctionCall[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( cfg, @@ -287,7 +287,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField(expr: FieldRead[V], defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralFieldInterpreter(state, + val r = new InterproceduralFieldInterpreter( + state, this, ps, fieldAccessInformation, @@ -305,8 +306,8 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[NonVirtualMethodCall]]s. */ private def processNonVirtualFunctionCall( - expr: NonVirtualFunctionCall[V], - defSite: Int + expr: NonVirtualFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, @@ -327,9 +328,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[VirtualMethodCall]]s. */ def processVirtualMethodCall( - expr: VirtualMethodCall[V], - defSite: Int, - callees: Callees + expr: VirtualMethodCall[V], + defSite: Int, + callees: Callees ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralVirtualMethodCallInterpreter( cfg, 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 708fc09b88..4c68e65fcb 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 @@ -26,12 +26,12 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) 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 6469e1e43f..6a2b7d9c05 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.fpcf.PropertyStore * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] @@ -50,7 +50,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { case "" => interpretInit(instr, e) 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 2dbf9a4178..63353a0724 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 @@ -32,13 +32,13 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class InterproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]], + declaredMethods: DeclaredMethods, + typeIterator: TypeIterator ) 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 1fbd0b96c0..f371048cd1 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 @@ -25,9 +25,9 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class InterproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualMethodCall[V] @@ -51,7 +51,7 @@ class InterproceduralVirtualMethodCallInterpreter( override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) - case _ => StringConstancyInformation.getNeutralElement + case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } 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 b68ff04e27..3754458ec8 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 @@ -27,10 +27,10 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class NewArrayPreparer( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NewArray[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 bbf5e155f3..47364e7d66 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 @@ -35,13 +35,13 @@ import org.opalj.tac.fpcf.analyses.cg.TypeIterator * @author Patrick Mell */ class VirtualFunctionCallPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]], - typeIterator: TypeIterator + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: InterproceduralComputationState, + declaredMethods: DeclaredMethods, + params: List[Seq[StringConstancyInformation]], + typeIterator: TypeIterator ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -159,7 +159,8 @@ class VirtualFunctionCallPreparationInterpreter( FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) + val entity = + (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { @@ -195,8 +196,8 @@ class VirtualFunctionCallPreparationInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[V], - defSite: Int + appendCall: VirtualFunctionCall[V], + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) val appendResult = valueOfAppendCall(appendCall, state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 97b81e6b08..709d1a2315 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -17,8 +17,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * @author Patrick Mell */ class ArrayLoadFinalizer( - state: InterproceduralComputationState, - 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/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala index 517ff633aa..ce11422ae1 100644 --- 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 @@ -14,8 +14,8 @@ import org.opalj.br.cfg.CFG * @author Patrick Mell */ class NewArrayFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = NewArray[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 3600043a34..a91422c001 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 @@ -17,8 +17,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType * @author Patrick Mell */ class VirtualFunctionCallFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, + cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = VirtualFunctionCall[V] @@ -33,7 +33,7 @@ class VirtualFunctionCallFinalizer( instr.name match { case "append" => finalizeAppend(instr, defSite) case "toString" => finalizeToString(instr, defSite) - case _ => state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) + case _ => state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) } } 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 9c8e5dce13..18729ab2ce 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 @@ -21,8 +21,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralArrayInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + 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/intraprocedural/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala index dabba7ec48..2c9d168bf8 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 @@ -22,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralFieldInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + 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/intraprocedural/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala index 1aa231829d..cefcde8f27 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 @@ -22,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralGetStaticInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + 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/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala index 68f260fafa..e8fe66b1be 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 @@ -20,8 +20,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + 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/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala index a3df8aedab..f535369095 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 @@ -22,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) 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/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala index c87da14f03..a931b7cfbb 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 @@ -22,8 +22,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) 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/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala index 92fe78cf7f..e437d5151c 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 @@ -30,8 +30,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] 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 50f65dfefb..0773248488 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 @@ -24,8 +24,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class IntraproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualMethodCall[V] @@ -51,7 +51,7 @@ class IntraproceduralVirtualMethodCallInterpreter( override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) - case _ => StringConstancyInformation.getNeutralElement + case _ => StringConstancyInformation.getNeutralElement } FinalEP(instr, StringConstancyProperty(sci)) } 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 50df27a434..45c6aeffb2 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 @@ -780,9 +780,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''else'' branch. */ protected def isCondWithoutElse( - branchingSite: Int, - cfg: CFG[Stmt[V], TACStmts[V]], - processedIfs: mutable.Map[Int, Unit] + branchingSite: Int, + cfg: CFG[Stmt[V], TACStmts[V]], + processedIfs: mutable.Map[Int, Unit] ): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative From 7c646426d31df677d4b7302f6ce0122c346c9c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 22:48:20 +0100 Subject: [PATCH 338/583] Cleanup stringtree and intraprocedural Somehow fixes two test cases --- .../string_definition/StringTree.scala | 70 ++++++------------- .../IntraproceduralStringAnalysis.scala | 36 ++++------ 2 files changed, 35 insertions(+), 71 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 dbf9637948..67c79ddf2b 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 @@ -74,12 +74,9 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * This is a helper function which processes the `reduce` operation for [[StringTreeConcat]] * elements. */ - private def processReduceConcat( - children: List[StringTree] - ): List[StringConstancyInformation] = { + private def processReduceConcat(children: List[StringTree]): 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) + // Stores whether we deal with a flat or nested structure (in the latter case maxNestingLevel >= 2) val maxNestingLevel = reducedLists.foldLeft(0) { (max: Int, next: List[StringConstancyInformation]) => Math.max(max, next.size) } @@ -150,10 +147,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques val times = if (lowerBound.isDefined && upperBound.isDefined) (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol - val reducedAcc = reduceAcc(c) - val reduced = if (reducedAcc.nonEmpty) reducedAcc.head - else - StringConstancyInformation.lb + val reduced = reduceAcc(c).headOption.getOrElse(StringConstancyInformation.lb) List(StringConstancyInformation( reduced.constancyLevel, reduced.constancyType, @@ -173,23 +167,17 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * @param children The children from which to remove duplicates. * @return Returns a list of [[StringTree]] with unique elements. */ - private def removeDuplicateTreeValues( - children: ListBuffer[StringTree] - ): ListBuffer[StringTree] = { + private def removeDuplicateTreeValues(children: ListBuffer[StringTree]): ListBuffer[StringTree] = { val seen = mutable.Map[StringConstancyInformation, Boolean]() - val unique = ListBuffer[StringTree]() - children.foreach { + + children.flatMap[StringTree] { case next @ StringTreeConst(sci) => if (!seen.contains(sci)) { seen += (sci -> true) - unique.append(next) - } - case loop: StringTreeRepetition => unique.append(loop) - case concat: StringTreeConcat => unique.append(concat) - case or: StringTreeOr => unique.append(or) - case cond: StringTreeCond => unique.append(cond) + Some(next) + } else None + case other => Some(other) } - unique } /** @@ -198,22 +186,13 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques private def simplifyAcc(subtree: StringTree): StringTree = { subtree match { case StringTreeOr(cs) => - val newChildren = cs.clone() + // Flatten any nested StringTreeOr elements into this one + val newChildren = ListBuffer.empty[StringTree] cs.foreach { - case nextC @ StringTreeOr(subChildren) => - simplifyAcc(nextC) - var insertIndex = newChildren.indexOf(nextC) - subChildren.foreach { next => - newChildren.insert(insertIndex, next) - insertIndex += 1 - } - newChildren.-=(nextC) - case _ => + case nextC: StringTreeOr => newChildren.appendAll(simplifyAcc(nextC).children) + case c => newChildren.append(c) } - val unique = removeDuplicateTreeValues(newChildren) - subtree.children.clear() - subtree.children.appendAll(unique) - subtree + StringTreeOr(removeDuplicateTreeValues(newChildren)) case stc: StringTreeCond => // If the child of a StringTreeCond is a StringTreeRepetition, replace the // StringTreeCond by the StringTreeRepetition element (otherwise, regular @@ -255,11 +234,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques val newRepElement = StringTreeRepetition(StringTreeOr(childrenOfReps)) val indexFirstChild = newChildren.indexOf(repetitionElements.head) - newChildren = newChildren.filter { - case _: StringTreeRepetition => false - case _ => true - } - + newChildren = newChildren.filter { !_.isInstanceOf[StringTreeRepetition] } newChildren.insert(indexFirstChild, newRepElement) if (newChildren.length == 1) { newChildren.head @@ -273,13 +248,11 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques } 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 + 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 } } @@ -329,7 +302,6 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * semantically different tree! */ def groupRepetitionElements(): StringTree = groupRepetitionElementsAcc(this) - } /** @@ -343,7 +315,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * Otherwise, the number of repetitions is computed by `upperBound - lowerBound`. */ case class StringTreeRepetition( - var child: StringTree, + child: StringTree, lowerBound: Option[Int] = None, upperBound: Option[Int] = None ) extends StringTree(ListBuffer(child)) 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 6a4cf8dece..f916ef257d 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 @@ -81,7 +81,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys def analyze(data: SContext): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyProperty.lb.stringConstancyInformation + var sci = StringConstancyInformation.lb // Retrieve TAC from property store val tacOpt: Option[TACode[TACMethodParameter, V]] = ps(data._2, TACAI.key) match { @@ -99,7 +99,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys val uVar = puVar.toValueOriginForm(tac.pcToIndex) 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 + // operations of strings (not String{Builder, Buffer}s, they will be handled further down if (defSites.head < 0) { return Result(data, StringConstancyProperty.lb) } @@ -114,38 +114,32 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys 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) { + // String{Builder,Buffer} from method parameter is to be evaluated return Result(data, StringConstancyProperty.lb) } - val paths = new WindowPathFinder(tac.cfg).findPaths(initDefSites, uVar.definedBy.head) - val leanPaths = paths.makeLeanPath(uVar, stmts) + val path = new WindowPathFinder(tac.cfg).findPaths(initDefSites, uVar.definedBy.head) + val leanPath = path.makeLeanPath(uVar, stmts) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPaths, stmts, puVar) + val dependentVars = findDependentVars(leanPath, stmts, puVar) if (dependentVars.nonEmpty) { + state = ComputationState(leanPath, dependentVars, mutable.Map[Int, StringConstancyInformation](), tac) dependentVars.keys.foreach { nextVar => - val toAnalyze = (nextVar, data._2) - val fpe2sci = mutable.Map[Int, StringConstancyInformation]() - state = ComputationState(leanPaths, dependentVars, fpe2sci, tac) - val ep = propertyStore(toAnalyze, StringConstancyProperty.key) - ep match { + propertyStore((nextVar, data._2), StringConstancyProperty.key) match { case finalEP: FinalEP[SContext, StringConstancyProperty] => return processFinalP(data, dependees.values.flatten, state, finalEP) - case _ => - if (!dependees.contains(data)) { - dependees(data) = ListBuffer() - } - dependees(data).append(ep) + case ep => + dependees.getOrElseUpdate(data, ListBuffer()).append(ep) } } } else { val interpretationHandler = IntraproceduralInterpretationHandler(tac) - sci = new PathTransformer(interpretationHandler).pathToStringTree(leanPaths).reduce(true) + sci = new PathTransformer(interpretationHandler).pathToStringTree(leanPath).reduce(true) } - } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings - else { + } else { + // We deal with pure strings val interpretationHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => @@ -167,9 +161,6 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys } } - /** - * Responsible for handling the case that the `propertyStore` outputs a [[FinalEP]]. - */ private def processFinalP( data: SContext, dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], @@ -244,6 +235,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys val param = outerExpr.asVirtualFunctionCall.params.head.asVar param.definedBy.filter(_ >= 0).foreach { ds => val expr = stmts(ds).asAssignment.expr + // TODO check support for passing nested string builder directly (e.g. with a test case) if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append((param.toPersistentForm(tac.stmts), fpe.element)) } From 762703c5cc693f2c319bcf5992b537c30886ff12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jan 2024 23:23:10 +0100 Subject: [PATCH 339/583] Cleanup dependency handling of intraprocedural analysis Fix nested string builder construction test --- .../IntraProceduralTestMethods.java | 24 +++++- .../IntraproceduralStringAnalysis.scala | 86 +++++++++---------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index 012f220894..50af5637ac 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -790,7 +790,7 @@ public void directAppendConcatsWith2ndStringBuilder() { expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" ) }) - public void secondStringBuilderRead(String className) { + public void complexSecondStringBuilderRead(String className) { StringBuilder sbObj = new StringBuilder("Object"); StringBuilder sbRun = new StringBuilder("Runtime"); @@ -806,6 +806,28 @@ public void secondStringBuilderRead(String className) { analyzeString(sb2.toString()); } + @StringDefinitionsCollection( + value = "checks if the case, where the value of a StringBuilder depends on the " + + "simple construction of a second StringBuilder is determined correctly.", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" + ) + }) + public void simpleSecondStringBuilderRead(String className) { + StringBuilder sbObj = new StringBuilder("Object"); + StringBuilder sbRun = new StringBuilder("Runtime"); + + StringBuilder sb1 = new StringBuilder("java.lang."); + if (sb1.length() == 0) { + sb1.append(sbObj.toString()); + } else { + sb1.append(sbRun.toString()); + } + + analyzeString(sb1.toString()); + } + @StringDefinitionsCollection( value = "an example that uses a non final field", stringDefinitions = { 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 f916ef257d..d58f30c11a 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 @@ -129,14 +129,19 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys dependentVars.keys.foreach { nextVar => propertyStore((nextVar, data._2), StringConstancyProperty.key) match { case finalEP: FinalEP[SContext, StringConstancyProperty] => - return processFinalP(data, dependees.values.flatten, state, finalEP) + if (dependees.contains(data)) + dependees(data) = dependees(data).filter { _.e != finalEP.e } + val sciOpt = processFinalP(dependees.values.flatten, state, finalEP) + if (sciOpt.isDefined) + sci = sciOpt.get case ep => dependees.getOrElseUpdate(data, ListBuffer()).append(ep) } } } else { val interpretationHandler = IntraproceduralInterpretationHandler(tac) - sci = new PathTransformer(interpretationHandler).pathToStringTree(leanPath).reduce(true) + val stringTree = new PathTransformer(interpretationHandler).pathToStringTree(leanPath) + sci = stringTree.reduce(true) } } else { // We deal with pure strings @@ -162,31 +167,22 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys } private def processFinalP( - data: SContext, dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], state: ComputationState, finalEP: FinalEP[SContext, StringConstancyProperty] - ): ProperPropertyComputationResult = { + ): Option[StringConstancyInformation] = { // Add mapping information (which will be used for computing the final result) state.fpe2sci.put(state.var2IndexMapping(finalEP.e._1), finalEP.p.stringConstancyInformation) - // No more dependees => Return the result for this analysis run - val remainingDependees = dependees.filter(_.e != finalEP.e) - if (remainingDependees.isEmpty) { + if (dependees.isEmpty) { val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) - val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( + val sci = new PathTransformer(interpretationHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } ).reduce(true) - Result(data, StringConstancyProperty(finalSci)) + Some(sci) } else { - InterimResult( - data, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - remainingDependees.toSet, - continuation(data, remainingDependees, state) - ) + None } } @@ -205,7 +201,26 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = eps match { case finalEP: FinalEP[_, _] => - processFinalP(data, dependees, state, finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]]) + val finalScpEP = finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]] + val sciOpt = processFinalP(dependees, state, finalScpEP) + if (sciOpt.isDefined) { + val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) + val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( + state.computedLeanPath, + state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } + ).reduce(true) + Result(data, StringConstancyProperty(finalSci)) + } else { + val remainingDependees = dependees.filter { _.e != finalScpEP.e } + InterimResult( + data, + StringConstancyProperty.ub, + StringConstancyProperty.lb, + remainingDependees.toSet, + continuation(data, remainingDependees, state) + ) + } + case InterimLUBP(lb, ub) => InterimResult(data, lb, ub, dependees.toSet, continuation(data, dependees, state)) case _ => throw new IllegalStateException("Could not process the continuation successfully.") } @@ -217,16 +232,11 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys */ private def findDependeesAcc( subpath: SubPath, - stmts: Array[Stmt[V]], - target: SEntity - )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { - var encounteredTarget = false + stmts: Array[Stmt[V]] + )(implicit tac: TACode[TACMethodParameter, V]): ListBuffer[(SEntity, Int)] = { val foundDependees = ListBuffer[(SEntity, Int)]() subpath match { case fpe: FlatPathElement => - if (target.toValueOriginForm(tac.pcToIndex).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 { @@ -243,17 +253,13 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys } case _ => } - (foundDependees, encounteredTarget) + foundDependees case npe: NestedPathElement => npe.element.foreach { nextSubpath => - if (!encounteredTarget) { - val (innerFoundDependees, seen) = findDependeesAcc(nextSubpath, stmts, target) - encounteredTarget = seen - foundDependees.appendAll(innerFoundDependees) - } + foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) } - (foundDependees, encounteredTarget) - case _ => (foundDependees, encounteredTarget) + foundDependees + case _ => foundDependees } } @@ -273,21 +279,11 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys ignore: SEntity )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() - val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), stmts) - var wasTargetSeen = false path.elements.foreach { nextSubpath => - if (!wasTargetSeen) { - val (currentDeps, encounteredTarget) = findDependeesAcc(nextSubpath, stmts, ignore) - wasTargetSeen = encounteredTarget - currentDeps.foreach { nextPair => - val newExpressions = InterpretationHandler.findNewOfVar( - nextPair._1.toValueOriginForm(tac.pcToIndex), - stmts - ) - if (ignore != nextPair._1 && ignoreNews != newExpressions) { - dependees.put(nextPair._1, nextPair._2) - } + findDependeesAcc(nextSubpath, stmts).foreach { nextPair => + if (ignore != nextPair._1) { + dependees.put(nextPair._1, nextPair._2) } } } From ba0f1e6b4c49772b806d48173463b7f15826d154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 28 Jan 2024 00:20:07 +0100 Subject: [PATCH 340/583] Cleanup path transformation --- .../preprocessing/AbstractPathFinder.scala | 111 +++++++----------- .../preprocessing/DefaultPathFinder.scala | 7 +- .../string_analysis/preprocessing/Path.scala | 18 ++- .../preprocessing/PathTransformer.scala | 64 ++++------ .../preprocessing/WindowPathFinder.scala | 7 +- 5 files changed, 77 insertions(+), 130 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 45c6aeffb2..c75b75245a 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 @@ -9,6 +9,7 @@ package preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.br.ObjectType import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG @@ -42,9 +43,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''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])] - ) + 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 @@ -292,8 +291,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @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 + // 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 => @@ -301,11 +300,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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) + val isThrowable = cn.catchType.isDefined && cn.catchType.get == ObjectType.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) @@ -313,9 +310,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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) { + } else if (cnSameStartPC.tail.isEmpty && !isThrowable) { + // 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 (cn.endPC > -1) { var end = cfg.bb(cn.endPC).successors.map { case bb: BasicBlock => bb.startPC - 1 @@ -330,9 +327,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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 { + } else { + // Otherwise, the index after the try and all catches marks the end index (-1 + // to not already get the start index of the successor) if (hasCatchFinally) { // Find out, how many elements the finally block has and adjust the try // block accordingly @@ -376,12 +373,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * 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 = { + private def buildHierarchy(element: CSInfo, children: Map[CSInfo, ListBuffer[CSInfo]]): HierarchicalCSOrder = { if (!children.contains(element)) { - // Recursion anchor (no children available + // Recursion anchor (no children available) HierarchicalCSOrder(List((Some(element), List()))) } else { HierarchicalCSOrder(List(( @@ -583,7 +577,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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") { + if (cn.catchType.isDefined && cn.catchType.get == ObjectType.Throwable) { throwableElement = Some(cn) } else { catchBlockStartPCs.append(cn.handlerPC) @@ -627,14 +621,12 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { catchBlockStartPCs.zipWithIndex.foreach { case (nextStart, i) => if (i + 1 < catchBlockStartPCs.length) { - startEndPairs.append( - (nextStart, catchBlockStartPCs(i + 1) - 1 - numElementsFinally) - ) + startEndPairs.append((nextStart, catchBlockStartPCs(i + 1) - 1 - numElementsFinally)) } } - } // In some cases (sometimes when a throwable is involved) the successors are no catch - // nodes => Find the bounds now - else { + } else { + // In some cases (sometimes when a throwable is involved) the successors are no catch + // nodes => Find the bounds now val cn = cfg.catchNodes.filter(_.startPC == start).head startEndPairs.append((cn.startPC, cn.endPC - 1)) val endOfCatch = cfg.code.instructions(cn.handlerPC - 1) match { @@ -942,10 +934,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @note For further details, see [[getStartAndEndIndexOfCondWithAlternative]], * [[getStartAndEndIndexOfCondWithoutAlternative]], and [[determineTryCatchBounds]]. */ - protected def processIf( - stmt: Int, - processedIfs: mutable.Map[Int, Unit] - ): CSInfo = { + protected def processIf(stmt: Int, processedIfs: mutable.Map[Int, Unit]): CSInfo = { val csType = determineTypeOfIf(stmt, processedIfs) val (startIndex, endIndex) = csType match { case NestedPathType.Repetition => @@ -953,10 +942,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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 _ => + case NestedPathType.CondWithAlternative => getStartAndEndIndexOfCondWithAlternative(stmt, processedIfs) + case t => + throw new IllegalArgumentException(s"Unexpected nested path type: $t") } (startIndex, endIndex, csType) } @@ -975,9 +964,7 @@ 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 caseGotoOption = caseStmts.reverse.find { caseIndex => - cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] - } + val caseGotoOption = caseStmts.findLast { caseIndex => cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] } // If no such case is present, find the next goto after the default case val posGoTo = if (caseGotoOption.isEmpty) { var i = switch.defaultStmt @@ -1036,9 +1023,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[CSInfo]]. The elements are returned in a sorted by ascending start index. */ 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) var foundCS = ListBuffer[CSInfo]() - // For a fast loop-up which if statements have already been processed + // For a fast look-up which statements have already been processed val processedIfs = mutable.Map[Int, Unit]() val processedSwitches = mutable.Map[Int, Unit]() val stack = mutable.Stack[CFGNode]() @@ -1083,31 +1069,27 @@ 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 + // 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 - 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 - } + // Add try-catch (only those that are relevant for the given start and end sites) information + val relevantTryCatchBlocks = determineTryCatchBounds() + // Filter out all blocks that completely surround the given start and end sites + .filter { + case (tryStart, tryEnd, _) => + !buildTryCatchPath(tryStart, tryEnd, fill = false)._2.exists { + case (nextInnerStart, nextInnerEnd) => + startSites.forall(_ >= nextInnerStart) && endSite <= nextInnerEnd + } + } + // Keep the try-catch blocks that are (partially) within the start and end sites + .filter { + case (tryStart, _, _) => startSites.exists(tryStart >= _) && tryStart <= endSite + } foundCS.appendAll(relevantTryCatchBlocks) foundCS.sortBy { case (start, _, _) => start }.toList @@ -1190,7 +1172,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { HierarchicalCSOrder(List(( None, - cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf)) + cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf.toMap)) ))) } @@ -1241,8 +1223,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // 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 + val isRepElement = + npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == NestedPathType.Repetition var lastInsertedIndex = 0 childrenPath.elements.foreach { nextEle => if (isRepElement) { @@ -1261,9 +1243,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // Compiler wants it but should never be the case! case _ => -1 } - if (insertIndex < startEndPairs.length && - lastInsertedIndex >= startEndPairs(insertIndex)._2 - ) { + if (insertIndex < startEndPairs.length && lastInsertedIndex >= startEndPairs(insertIndex)._2) { insertIndex += 1 } } @@ -1329,5 +1309,4 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * procedures using results of this function do not need to re-process). */ def findPaths(startSites: List[Int], endSite: Int): Path - } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index d2a0b866b5..5a4706e97c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -40,14 +40,13 @@ class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFind 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) { + // In case the are no control structures, return a path from the first to the last element Path(cfg.startBlock.startPC.until(endSite).map(FlatPathElement).toList) - } // Otherwise, order the control structures and assign the corresponding path elements - else { + } else { + // Otherwise, order the control structures and assign the corresponding path elements val orderedCS = hierarchicallyOrderControlStructures(csInfo) hierarchyToPath(orderedCS.hierarchy.head._2, startSite, endSite) } } - } 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 fbbf9fb5a7..41aad7a8c5 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 @@ -6,6 +6,8 @@ package analyses package string_analysis package preprocessing +import scala.annotation.tailrec + import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -56,7 +58,6 @@ case object NestedPathType extends Enumeration { * This type is to mark `try-catch` or `try-catch-finally` constructs. */ val TryCatchFinally: NestedPathType.Value = Value - } /** @@ -129,7 +130,7 @@ 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] = { + @tailrec private def removeOuterBranching(npe: NestedPathElement): ListBuffer[SubPath] = { if (npe.element.tail.isEmpty) { npe.element.head match { case innerNpe: NestedPathElement => removeOuterBranching(innerNpe) @@ -143,14 +144,11 @@ case class Path(elements: List[SubPath]) { /** * 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 + * 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 = { + private def stripUnnecessaryBranches(npe: NestedPathElement, endSite: Int): NestedPathElement = { npe.element.foreach { case innerNpe: NestedPathElement => if (innerNpe.elementType.isEmpty) { @@ -334,7 +332,6 @@ case class Path(elements: List[SubPath]) { Path(leanPath.toList) } - } object Path { @@ -342,7 +339,7 @@ object Path { /** * Returns the very last [[FlatPathElement]] in this path, respecting any nesting structure. */ - def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { + @tailrec def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { npe.element.last match { case fpe: FlatPathElement => fpe case npe: NestedPathElement => @@ -354,5 +351,4 @@ object Path { case _ => FlatPathElement(-1) } } - } 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 cd273ce220..46f99c279c 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 @@ -9,7 +9,6 @@ package preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -17,6 +16,8 @@ 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.fpcf.FinalP +import org.opalj.fpcf.InterimUBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -45,31 +46,16 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { val sci = if (fpe2Sci.contains(fpe.element)) { StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { - val r = interpretationHandler.processDefSite(fpe.element) - 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 - } - } + val sciToAdd = interpretationHandler.processDefSite(fpe.element) match { + case FinalP(p) => p.stringConstancyInformation + case InterimUBP(ub) => ub.stringConstancyInformation + case _ => StringConstancyInformation.lb } + fpe2Sci(fpe.element) = ListBuffer(sciToAdd) sciToAdd } - if (sci.isTheNeutralElement) { - None - } else { - Some(StringTreeConst(sci)) - } + Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) case npe: NestedPathElement => if (npe.elementType.isDefined) { npe.elementType.get match { @@ -81,23 +67,20 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { ) Some(StringTreeRepetition(processedSubPath)) case _ => - val processedSubPaths = - npe.element.map { ne => pathToTreeAcc(ne, fpe2Sci) }.filter(_.isDefined).map(_.get) + val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne, fpe2Sci) } if (processedSubPaths.nonEmpty) { npe.elementType.get match { 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) + // 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 + case NestedPathType.CondWithoutAlternative => Some(StringTreeCond(processedSubPaths)) + case _ => None } } else { None @@ -108,8 +91,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { case 0 => None case 1 => pathToTreeAcc(npe.element.head, fpe2Sci) case _ => - val processed = - npe.element.map { ne => pathToTreeAcc(ne, fpe2Sci) }.filter(_.isDefined).map(_.get) + val processed = npe.element.flatMap { ne => pathToTreeAcc(ne, fpe2Sci) } if (processed.isEmpty) { None } else { @@ -154,19 +136,14 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { 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) - ) + pathToTreeAcc(path.elements.head, fpe2Sci).getOrElse(StringTreeConst(StringConstancyInformation.lb)) case _ => - val concatElement = StringTreeConcat(ListBuffer.from(path.elements.map { ne => - pathToTreeAcc(ne, fpe2Sci) - }.filter(_.isDefined).map(_.get))) - // 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) { - concatElement.children.head + val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_, fpe2Sci) }) + if (children.size == 1) { + // The concatenation of one child is the child itself + children.head } else { - concatElement + StringTreeConcat(children) } } if (resetExprHandler) { @@ -174,5 +151,4 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } tree } - } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index c7e89498db..c2749b043c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -35,15 +35,13 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde * @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 + // 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 iff: If[V] if startSites.contains(iff.targetStmt) => startSite = Some(nextStmt) case _: Switch[V] => val (startSwitch, endSwitch, _) = processSwitch(nextStmt) val isParentSwitch = startSites.forall { @@ -74,5 +72,4 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde hierarchyToPath(orderedCS.hierarchy.head._2, startSite.get, endSite) } } - } From b54c1c9dc05e64325bf43bcc9dfef074f3e7177b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 28 Jan 2024 13:33:09 +0100 Subject: [PATCH 341/583] Fix initialization handling in field access information key --- .../org/opalj/br/analyses/FieldAccessInformationKey.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala index 5495fc3418..b1314562a5 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala @@ -26,6 +26,10 @@ object FieldAccessInformationKey extends ProjectInformationKey[FieldAccessInform "analysis configuration", s"no field access information analysis configured, using SimpleFieldAccessInformationAnalysis as a fallback" )(project.logContext) + project.updateProjectInformationKeyInitializationData(this) { + case None => Seq(EagerSimpleFieldAccessInformationAnalysis) + case Some(schedulers) => schedulers ++ Seq(EagerSimpleFieldAccessInformationAnalysis) + } Seq(EagerSimpleFieldAccessInformationAnalysis) } From 4a1f3de09528edeb059762a48575bb28d3ff99b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 28 Jan 2024 13:34:02 +0100 Subject: [PATCH 342/583] Convert interprocedural analysis to contextprovider --- .../InterproceduralStringAnalysis.scala | 43 ++++++++----------- .../AbstractStringInterpreter.scala | 7 ++- ...InterproceduralInterpretationHandler.scala | 16 +++---- ...ralNonVirtualFunctionCallInterpreter.scala | 6 +-- ...ceduralStaticFunctionCallInterpreter.scala | 9 ++-- ...alFunctionCallPreparationInterpreter.scala | 13 ++---- 6 files changed, 39 insertions(+), 55 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 4ec027c0a0..84d4c8015e 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 @@ -7,16 +7,17 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.FieldType import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers @@ -33,8 +34,6 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.cg.RTATypeIterator -import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler @@ -78,8 +77,7 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { - // TODO: How do we pass the type iterator along? How do we decide which to use? - private val typeIterator: TypeIterator = new RTATypeIterator(project) + private val contextProvider: ContextProvider = project.get(ContextProviderKey) // TODO: Is it possible to make the following two parameters configurable from the outside? /** @@ -101,7 +99,7 @@ class InterproceduralStringAnalysis( private val fieldWriteThreshold = 100 private val declaredMethods = project.get(DeclaredMethodsKey) private val declaredFields = project.get(DeclaredFieldsKey) - private final val fieldAccessInformation = project.get(FieldAccessInformationKey) + private val fieldAccessInformation = project.get(FieldAccessInformationKey) /** * Returns the current interim result for the given state. If required, custom lower and upper @@ -188,7 +186,7 @@ class InterproceduralStringAnalysis( declaredFields, fieldAccessInformation, state, - typeIterator + contextProvider ) val interimState = state.copy() interimState.tac = state.tac @@ -203,7 +201,7 @@ class InterproceduralStringAnalysis( declaredFields, fieldAccessInformation, interimState, - typeIterator + contextProvider ) } @@ -424,12 +422,12 @@ class InterproceduralStringAnalysis( case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = eps :: state.dependees - val uvar = eps.e.asInstanceOf[SContext]._1 - state.var2IndexMapping(uvar).foreach { i => + val puVar = eps.e.asInstanceOf[SContext]._1 + state.var2IndexMapping(puVar).foreach { i => state.appendToInterimFpe2Sci( i, ub.stringConstancyInformation, - Some(uvar) + Some(puVar) ) } getInterimResult(state) @@ -513,7 +511,7 @@ class InterproceduralStringAnalysis( state: InterproceduralComputationState ): Boolean = { - val callers = state.callers.callers(state.dm)(typeIterator).iterator.toSeq + val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq if (callers.length > callersThreshold) { state.params.append( ListBuffer.from(state.entity._2.parameterTypes.map { @@ -890,7 +888,6 @@ object InterproceduralStringAnalysis { } StringConstancyInformation(StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings) } - } sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { @@ -912,12 +909,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} - override def afterPhaseCompletion( - p: SomeProject, - ps: PropertyStore, - analysis: FPCFAnalysis - ): Unit = {} - + override def afterPhaseCompletion(p: SomeProject, ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} } /** @@ -926,11 +918,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule object LazyInterproceduralStringAnalysis extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { - override def register( - p: SomeProject, - ps: PropertyStore, - analysis: InitializationData - ): FPCFAnalysis = { + override def register(p: SomeProject, ps: PropertyStore, analysis: InitializationData): FPCFAnalysis = { val analysis = new InterproceduralStringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis @@ -938,6 +926,9 @@ object LazyInterproceduralStringAnalysis override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - // TODO: Needs TAC key?? - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, FieldAccessInformationKey) + override def requiredProjectInformation: ProjectInformationKeys = Seq( + DeclaredMethodsKey, + FieldAccessInformationKey, + ContextProviderKey, + ) } 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 bf22165835..7161543a63 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,10 +8,10 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees @@ -20,7 +20,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.cg.TypeIterator import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation @@ -81,12 +80,12 @@ abstract class AbstractStringInterpreter( pc: Int, ps: PropertyStore, callees: Callees, - typeIt: TypeIterator + contextProvider: ContextProvider ): (List[Method], Boolean) = { var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() - callees.callees(NoContext, pc)(ps, typeIt).map(_.method).foreach { + callees.callees(NoContext, pc)(ps, contextProvider).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => hasMethodWithUnknownBody = true } 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 09b6ab254b..51eaaaeeff 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 @@ -11,6 +11,7 @@ import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -20,7 +21,6 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.tac.fpcf.analyses.cg.TypeIterator 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 @@ -53,7 +53,7 @@ class InterproceduralInterpretationHandler( declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, - typeIterator: TypeIterator + contextProvider: ContextProvider ) extends InterpretationHandler(tac) { /** @@ -212,7 +212,7 @@ class InterproceduralInterpretationHandler( state, declaredMethods, params, - typeIterator + contextProvider ).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 @@ -260,7 +260,7 @@ class InterproceduralInterpretationHandler( state, params, declaredMethods, - typeIterator + contextProvider ).interpret(expr, defSite) if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -293,7 +293,7 @@ class InterproceduralInterpretationHandler( ps, fieldAccessInformation, declaredFields, - typeIterator + contextProvider ).interpret(expr, defSite) if (r.isRefinable) { processedDefSites.remove(defSite) @@ -315,7 +315,7 @@ class InterproceduralInterpretationHandler( ps, state, declaredMethods, - typeIterator + contextProvider ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -435,7 +435,7 @@ object InterproceduralInterpretationHandler { declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, - typeIterator: TypeIterator + contextProvider: ContextProvider ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( tac, ps, @@ -443,6 +443,6 @@ object InterproceduralInterpretationHandler { declaredFields, fieldAccessInformation, state, - typeIterator + contextProvider ) } 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 4c68e65fcb..afdd8a9f41 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 @@ -9,13 +9,13 @@ package interprocedural import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing @@ -31,7 +31,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + contextProvider: ContextProvider ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] @@ -48,7 +48,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val methods = getMethodsForPC(instr.pc, ps, state.callees, typeIterator) + val methods = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) if (methods._1.isEmpty) { // No methods available => Return lower bound return FinalEP(instr, 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 63353a0724..c19d260729 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,9 +8,9 @@ package interpretation package interprocedural import scala.util.Try - import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -19,7 +19,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -38,7 +37,7 @@ class InterproceduralStaticFunctionCallInterpreter( state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]], declaredMethods: DeclaredMethods, - typeIterator: TypeIterator + contextProvider: ContextProvider ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] @@ -99,7 +98,7 @@ class InterproceduralStaticFunctionCallInterpreter( instr: StaticFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC(instr.pc, ps, state.callees, typeIterator) + val methods, _ = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) // Static methods cannot be overwritten, thus // 1) we do not need the second return value of getMethodsForPC and @@ -112,7 +111,7 @@ class InterproceduralStaticFunctionCallInterpreter( val m = methods._1.head val (_, tac) = getTACAI(ps, m, state) - val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) + val directCallSites = state.callees.directCallSites(NoContext)(ps, contextProvider) 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 47364e7d66..5ea853ab01 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 @@ -12,6 +12,7 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.ObjectType import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -23,7 +24,6 @@ import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.cg.TypeIterator /** * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing @@ -41,7 +41,7 @@ class VirtualFunctionCallPreparationInterpreter( state: InterproceduralComputationState, declaredMethods: DeclaredMethods, params: List[Seq[StringConstancyInformation]], - typeIterator: TypeIterator + contextProvider: ContextProvider ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -103,18 +103,13 @@ class VirtualFunctionCallPreparationInterpreter( * finalized later on. */ private def interpretArbitraryCall(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val (methods, _) = getMethodsForPC( - instr.pc, - ps, - state.callees, - typeIterator - ) + val (methods, _) = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) if (methods.isEmpty) { return FinalEP(instr, StringConstancyProperty.lb) } // TODO: Type Iterator! - val directCallSites = state.callees.directCallSites(NoContext)(ps, typeIterator) + val directCallSites = state.callees.directCallSites(NoContext)(ps, contextProvider) val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava val relevantPCs = directCallSites.filter { From 2b9940cd43e90fb8dc9847bfc73ed88726fbe8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 1 Feb 2024 13:40:12 +0100 Subject: [PATCH 343/583] Simplify switch statement processing a bit --- .../preprocessing/AbstractPathFinder.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 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 c75b75245a..2c4d837163 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 @@ -511,18 +511,20 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { end: Int, fill: Boolean ): (Path, List[(Int, Int)]) = { - val startEndPairs = ListBuffer[(Int, Int)]() val switch = cfg.code.instructions(start).asSwitch val caseStmts = ListBuffer[Int](switch.caseStmts.sorted: _*) - val containsDefault = caseStmts.length == caseStmts.distinct.length + // When the default stmt matches any case block, there is no explicit default defined + val containsDefault = !caseStmts.contains(switch.defaultStmt) if (containsDefault) { caseStmts.append(switch.defaultStmt) } + // TODO this does not make sense for all path types val pathType = if (containsDefault) NestedPathType.CondWithAlternative - else - NestedPathType.CondWithoutAlternative + else NestedPathType.CondWithoutAlternative + // Determine the start and end stmt indices of each case stmt + val startEndPairs = ListBuffer[(Int, Int)]() var previousStart = caseStmts.head caseStmts.tail.foreach { nextStart => val currentEnd = nextStart - 1 @@ -535,14 +537,12 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { 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)) - } + val subPaths: ListBuffer[SubPath] = startEndPairs.map { pair => + val subpathElements = ListBuffer[SubPath]() + if (fill) { + subpathElements.appendAll(Range.inclusive(pair._1, pair._2).map(FlatPathElement)) + } + NestedPathElement(subpathElements, None) } (Path(List(NestedPathElement(subPaths, Some(pathType)))), startEndPairs.toList) } From c01be5f668f3c11dd281e785bf3894e632f2aadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 1 Feb 2024 15:07:59 +0100 Subject: [PATCH 344/583] Simplify and fix path leaning and processing --- .../IntraProceduralTestMethods.java | 191 ++++++++++++++++-- .../preprocessing/AbstractPathFinder.scala | 64 +++--- .../string_analysis/preprocessing/Path.scala | 119 ++++------- .../preprocessing/PathTransformer.scala | 11 +- .../preprocessing/WindowPathFinder.scala | 4 +- 5 files changed, 261 insertions(+), 128 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index 50af5637ac..c7718c548d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -227,25 +227,164 @@ public void multipleDefSites(int value) { } @StringDefinitionsCollection( - value = "a case where multiple optional definition sites have to be considered.", + value = "Switch statement with multiple relevant and multiple irrelevant cases", stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "a(b|c)?" ) }) - public void multipleOptionalAppendSites(int value) { + public void switchRelevantAndIrrelevant(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; + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 3: + break; + case 4: + break; + } + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)?" + ) + }) + public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "Switch statement with multiple relevant and multiple irrelevant cases and an irrelevant default case", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|c)?" + ) + }) + public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + break; + } + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "Switch statement with multiple relevant and no irrelevant cases and a relevant default case", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)" + ) + }) + public void switchRelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "Switch statement a relevant default case and a nested switch statement", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d)?|f)" + ) + }) + public void switchNestedNoNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + } + break; + default: + sb.append("f"); + break; + } + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "Switch statement a relevant default case and a nested switch statement", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d|e)|f)" + ) + }) + public void switchNestedWithNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + default: + sb.append("e"); + break; + } + break; + default: + sb.append("f"); + break; } analyzeString(sb.toString()); } @@ -347,6 +486,30 @@ public void ifElseWithStringBuilder3() { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a((bcd|xyz))?" + ) + }) + public void ifElseWithStringBuilder4() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 3 == 0) { + sb.append("b"); + sb.append("c"); + sb.append("d"); + } else if (i % 2 == 0) { + System.out.println("something"); + } else { + sb.append("x"); + sb.append("y"); + sb.append("z"); + } + analyzeString(sb.toString()); + } + @StringDefinitionsCollection( value = "simple for loops with known and unknown bounds", stringDefinitions = { @@ -574,7 +737,7 @@ public void withException(String filename) { value = "case with a try-catch-finally exception", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*)?" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" @@ -583,7 +746,7 @@ public void withException(String filename) { expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ) }) - public void tryCatchFinally(String filename) { + public void tryCatchFinally(String filename) { // TODO why three definitions here? StringBuilder sb = new StringBuilder("====="); try { String data = new String(Files.readAllBytes(Paths.get(filename))); 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 2c4d837163..32ede99d7c 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 @@ -509,19 +509,17 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { private def buildPathForSwitch( start: Int, end: Int, + pathType: NestedPathType.Value, fill: Boolean ): (Path, List[(Int, Int)]) = { val switch = cfg.code.instructions(start).asSwitch val caseStmts = ListBuffer[Int](switch.caseStmts.sorted: _*) // When the default stmt matches any case block, there is no explicit default defined - val containsDefault = !caseStmts.contains(switch.defaultStmt) + val containsDefault = pathType == NestedPathType.SwitchWithDefault if (containsDefault) { caseStmts.append(switch.defaultStmt) } - // TODO this does not make sense for all path types - val pathType = if (containsDefault) NestedPathType.CondWithAlternative - else NestedPathType.CondWithoutAlternative // Determine the start and end stmt indices of each case stmt val startEndPairs = ListBuffer[(Int, Int)]() @@ -953,8 +951,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { /** * 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. + * [[NestedPathType.SwitchWithDefault]] if the `switch` has a `default` case and + * [[NestedPathType.SwitchWithoutDefault]] otherwise. * * @param stmt The index of the statement to process. This statement must be of type [[Switch]]. * @@ -980,10 +978,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { end = posGoTo } - val containsDefault = caseStmts.length == caseStmts.distinct.length - val pathType = if (containsDefault) NestedPathType.CondWithAlternative - else - NestedPathType.CondWithoutAlternative + val containsDefault = !caseStmts.contains(switch.defaultStmt) + val pathType = if (containsDefault) NestedPathType.SwitchWithDefault + else NestedPathType.SwitchWithoutDefault (stmt, end, pathType) } @@ -1102,25 +1099,24 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[buildPathForSwitch]], and [[buildTryCatchPath]]. */ protected def buildPathForElement( - toTransform: HierarchicalCSOrder, + toTransform: CSInfo, 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) - } + val start = toTransform._1 + val end = toTransform._2 + toTransform._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.SwitchWithDefault => + buildPathForSwitch(start, end, NestedPathType.SwitchWithDefault, fill) + case NestedPathType.SwitchWithoutDefault => + buildPathForSwitch(start, end, NestedPathType.SwitchWithoutDefault, fill) + case NestedPathType.TryCatchFinally => + buildTryCatchPath(start, end, fill) } } @@ -1203,23 +1199,25 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // Recursively transform the hierarchies to paths topElements.foreach { nextTopEle => + val nextTopCsInfo = nextTopEle.hierarchy.head._1.get + // Build path up to the next control structure - val nextCSStart = nextTopEle.hierarchy.head._1.get._1 + val nextCSStart = nextTopCsInfo._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) + val (subpath, _) = buildPathForElement(nextTopCsInfo, 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 startIndex = nextTopCsInfo._1 + val endIndex = nextTopCsInfo._2 val childrenPath = hierarchyToPath(children, startIndex, endIndex) var insertIndex = 0 - val (subpath, startEndPairs) = buildPathForElement(nextTopEle, fill = false) + val (subpath, startEndPairs) = buildPathForElement(nextTopCsInfo, 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] @@ -1279,7 +1277,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ) finalPath.append(subPathToAdd) } - indexLastCSEnd = nextTopEle.hierarchy.head._1.get._2 + 1 + indexLastCSEnd = nextTopCsInfo._2 + 1 } finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement)) 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 41aad7a8c5..ac1681736a 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 @@ -54,6 +54,16 @@ case object NestedPathType extends Enumeration { */ val CondWithoutAlternative: NestedPathType.Value = Value + /** + * Use this type to mark a switch that does not contain a `default` statement. + */ + val SwitchWithoutDefault: NestedPathType.Value = Value + + /** + * Use this type to mark a switch that contains a `default` statement. + */ + val SwitchWithDefault: NestedPathType.Value = Value + /** * This type is to mark `try-catch` or `try-catch-finally` constructs. */ @@ -177,72 +187,40 @@ case class Path(elements: List[SubPath]) { * 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], - endSite: Int, - includeAlternatives: Boolean = false - ): (Option[NestedPathElement], Boolean) = { + endSite: Int + ): Option[NestedPathElement] = { val elements = ListBuffer[SubPath]() - var stop = false - var hasTargetBeenSeen = false - val isTryCatch = includeAlternatives || (toProcess.elementType.isDefined && - toProcess.elementType.get == NestedPathType.TryCatchFinally) - 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 _ => + toProcess.element.foreach { + case fpe: FlatPathElement => + if (siteMap.contains(fpe.element)) { + elements.append(fpe.copy()) } - } + case npe: NestedPathElement => + val leanedSubPath = makeLeanPathAcc(npe, siteMap, endSite) + val keepAlternativeBranches = toProcess.elementType match { + case Some(NestedPathType.CondWithAlternative) | + Some(NestedPathType.SwitchWithDefault) | + Some(NestedPathType.TryCatchFinally) => true + case _ => false + } + if (leanedSubPath.isDefined) { + elements.append(leanedSubPath.get) + } else if (keepAlternativeBranches) { + elements.append(NestedPathElement(ListBuffer[SubPath](), None)) + } + case _ => } if (elements.nonEmpty) { - (Some(NestedPathElement(elements, toProcess.elementType)), hasTargetBeenSeen) + Some(NestedPathElement(elements, toProcess.elementType)) } else { - (None, false) + None } } @@ -280,34 +258,21 @@ case class Path(elements: List[SubPath]) { }.map { s => (s, ()) }.toMap var leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.toArray.max - 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, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) - if (npe.elementType.isDefined && - npe.elementType.get != NestedPathType.TryCatchFinally - ) { - reachedEndSite = wasTargetSeen - } - if (leanedPath.isDefined) { - leanPath.append(leanedPath.get) - } - case _ => + elements.foreach { + case fpe: FlatPathElement if siteMap.contains(fpe.element) && fpe.element <= endSite => + leanPath.append(fpe) + case npe: NestedPathElement => + val leanedPath = makeLeanPathAcc(npe, siteMap, endSite) + if (leanedPath.isDefined) { + leanPath.append(leanedPath.get) } - } + case _ => } // 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) + // body in any case (as there is no alternative branch to consider) TODO check loops again what is with loops that are never executed? if (leanPath.tail.isEmpty) { leanPath.head match { case npe: NestedPathElement @@ -322,7 +287,7 @@ case class Path(elements: List[SubPath]) { leanPath.last match { case npe: NestedPathElement if npe.elementType.isDefined && - (npe.elementType.get != NestedPathType.TryCatchFinally) => + (npe.elementType.get != NestedPathType.TryCatchFinally || npe.elementType.get != NestedPathType.SwitchWithDefault) => val newLast = stripUnnecessaryBranches(npe, endSite) leanPath.remove(leanPath.size - 1) leanPath.append(newLast) 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 46f99c279c..6837d14922 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 @@ -70,8 +70,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne, fpe2Sci) } if (processedSubPaths.nonEmpty) { npe.elementType.get match { - case NestedPathType.CondWithAlternative | - NestedPathType.TryCatchFinally => + case NestedPathType.TryCatchFinally => // 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) { @@ -79,7 +78,15 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { } else { Some(StringTreeCond(processedSubPaths)) } + case NestedPathType.SwitchWithDefault | + NestedPathType.CondWithAlternative => + if (npe.element.size == processedSubPaths.size) { + Some(StringTreeOr(processedSubPaths)) + } else { + Some(StringTreeCond(ListBuffer(StringTreeOr(processedSubPaths)))) + } case NestedPathType.CondWithoutAlternative => Some(StringTreeCond(processedSubPaths)) + case NestedPathType.SwitchWithoutDefault => Some(StringTreeCond(processedSubPaths)) case _ => None } } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index c2749b043c..7d2fb5b471 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -66,8 +66,8 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde 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 { + } else { + // Otherwise, order the control structures and assign the corresponding path elements val orderedCS = hierarchicallyOrderControlStructures(csInfo) hierarchyToPath(orderedCS.hierarchy.head._2, startSite.get, endSite) } From 935b913b0a0bd37e7c2a619335259ebadcb442f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 1 Feb 2024 16:04:21 +0100 Subject: [PATCH 345/583] Reduce nesting of string expressions --- .../IntraProceduralTestMethods.java | 4 ++-- .../string_definition/StringTree.scala | 19 +++++++++++++------ .../preprocessing/PathTransformer.scala | 10 ++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index c7718c548d..e04596f090 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -490,7 +490,7 @@ public void ifElseWithStringBuilder3() { value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a((bcd|xyz))?" + expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)?" ) }) public void ifElseWithStringBuilder4() { @@ -862,7 +862,7 @@ public void replaceExamples(int value) { @StringDefinitions( // The bytecode produces an "if" within an "if" inside the first loop, // => two conds - expectedLevel = CONSTANT, expectedStrings = "abc(((d)?)?)*" + expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "" 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 67c79ddf2b..97790f7ca8 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 @@ -47,13 +47,20 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques 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}" + var possibleStrings = appendSci.get.possibleStrings if (processOr) { if (appendElements.tail.nonEmpty) { possibleStrings = s"($possibleStrings)" } } else { - possibleStrings = s"(${appendSci.get.possibleStrings})?" + // IMPROVE dont wrap and immediately unwrap in () + if (possibleStrings.startsWith("(") && possibleStrings.endsWith(")")) { + possibleStrings = s"(${possibleStrings.substring(1, possibleStrings.length - 1)})?" + } else if (possibleStrings.startsWith("(") && possibleStrings.endsWith(")?")) { + possibleStrings = s"(${possibleStrings.substring(1, possibleStrings.length - 2)})?" + } else { + possibleStrings = s"(${appendSci.get.possibleStrings})?" + } } scis.append(StringConstancyInformation( appendSci.get.constancyLevel, @@ -155,7 +162,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques )) case StringTreeConcat(cs) => processReduceConcat(cs.toList) case StringTreeOr(cs) => processReduceCondOrReduceOr(cs.toList) - case StringTreeCond(cs) => processReduceCondOrReduceOr(cs.toList, processOr = false) + case StringTreeCond(c) => processReduceCondOrReduceOr(List(c), processOr = false) case StringTreeConst(sci) => List(sci) } } @@ -250,7 +257,7 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques subtree match { case sto: StringTreeOr => processConcatOrOrCase(sto) case stc: StringTreeConcat => processConcatOrOrCase(stc) - case StringTreeCond(cs) => StringTreeCond(cs.map(groupRepetitionElementsAcc)) + case StringTreeCond(child) => StringTreeCond(groupRepetitionElementsAcc(child)) case StringTreeRepetition(child, _, _) => StringTreeRepetition(groupRepetitionElementsAcc(child)) case stc: StringTreeConst => stc } @@ -352,8 +359,8 @@ case class StringTreeOr( * string may have (contain) a particular but not necessarily. */ case class StringTreeCond( - override val children: ListBuffer[StringTree] -) extends StringTree(children) + child: StringTree +) extends StringTree(ListBuffer(child)) /** * [[StringTreeConst]]s are the only elements which are supposed to act as leafs within a 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 6837d14922..0511d28226 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 @@ -76,17 +76,19 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { if (processedSubPaths.tail.nonEmpty) { Some(StringTreeOr(processedSubPaths)) } else { - Some(StringTreeCond(processedSubPaths)) + Some(StringTreeCond(processedSubPaths.head)) } case NestedPathType.SwitchWithDefault | NestedPathType.CondWithAlternative => if (npe.element.size == processedSubPaths.size) { Some(StringTreeOr(processedSubPaths)) } else { - Some(StringTreeCond(ListBuffer(StringTreeOr(processedSubPaths)))) + Some(StringTreeCond(StringTreeOr(processedSubPaths))) } - case NestedPathType.CondWithoutAlternative => Some(StringTreeCond(processedSubPaths)) - case NestedPathType.SwitchWithoutDefault => Some(StringTreeCond(processedSubPaths)) + case NestedPathType.SwitchWithoutDefault => + Some(StringTreeCond(StringTreeOr(processedSubPaths))) + case NestedPathType.CondWithoutAlternative => + Some(StringTreeCond(StringTreeOr(processedSubPaths))) case _ => None } } else { From a5469631d59acc282bc7332b8d21cc579ebca25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 4 Feb 2024 19:37:03 +0100 Subject: [PATCH 346/583] Start refactor of interprocedural analysis --- .../InterproceduralComputationState.scala | 1 - .../InterproceduralStringAnalysis.scala | 156 ++++++------------ .../AbstractStringInterpreter.scala | 3 +- ...InterproceduralInterpretationHandler.scala | 17 +- ...ralNonVirtualFunctionCallInterpreter.scala | 2 +- ...ceduralStaticFunctionCallInterpreter.scala | 6 +- ...duralVirtualFunctionCallInterpreter.scala} | 15 +- .../VirtualFunctionCallFinalizer.scala | 2 +- 8 files changed, 73 insertions(+), 129 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/{VirtualFunctionCallPreparationInterpreter.scala => InterproceduralVirtualFunctionCallInterpreter.scala} (97%) 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 c95837a91a..eb22169b95 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 @@ -263,5 +263,4 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, } } } - } 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 84d4c8015e..0a25e3cb96 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 @@ -29,7 +29,6 @@ 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 @@ -235,7 +234,7 @@ class InterproceduralStringAnalysis( InterproceduralStringAnalysis.isSupportedType } if (hasSupportedParamType) { - hasParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) + hasFormalParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) } else { !hasCallersOrParamInfo } @@ -265,8 +264,6 @@ class InterproceduralStringAnalysis( state.isSetupCompleted = true } - // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyProperty.lb.stringConstancyInformation // 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.map(_.toList)) @@ -290,33 +287,36 @@ class InterproceduralStringAnalysis( } else { attemptFinalResultComputation = true } - } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings - else { + } else { + // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings attemptFinalResultComputation = true } - if (attemptFinalResultComputation) { - if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { - // 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) + var sci = StringConstancyInformation.lb + if ( + attemptFinalResultComputation + && state.dependees.isEmpty + && computeResultsForPath(state.computedLeanPath, state) + ) { + // 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) } } @@ -475,20 +475,14 @@ class InterproceduralStringAnalysis( Result(state.entity, StringConstancyProperty(finalSci)) } - /** - * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a - * [[org.opalj.fpcf.FinalP]]. - */ private def processFinalP( state: InterproceduralComputationState, e: Entity, - p: Property + p: StringConstancyProperty ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) - val retrievedProperty = p.asInstanceOf[StringConstancyProperty] - val currentSci = retrievedProperty.stringConstancyInformation state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { - state.appendToFpe2Sci(_, currentSci) + state.appendToFpe2Sci(_, p.stringConstancyInformation) } state.dependees = state.dependees.filter(_.e != e) @@ -507,10 +501,7 @@ class InterproceduralStringAnalysis( * 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 = { - + private def registerParams(state: InterproceduralComputationState): Boolean = { val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq if (callers.length > callersThreshold) { state.params.append( @@ -600,9 +591,7 @@ class InterproceduralStringAnalysis( Path(npe.element.toList), state ) - if (hasFinalResult) { - hasFinalResult = subFinalResult - } + hasFinalResult = hasFinalResult && subFinalResult case _ => } @@ -672,23 +661,23 @@ class InterproceduralStringAnalysis( } } - private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { - def hasExprParamUsage(expr: Expr[V]): Boolean = expr match { + private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { + def hasExprFormalParamUsage(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 fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) + case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) + case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) case _ => false } path.elements.exists { case FlatPathElement(index) => stmts(index) match { - case Assignment(_, _, expr) => hasExprParamUsage(expr) - case ExprStmt(_, expr) => hasExprParamUsage(expr) + case Assignment(_, _, expr) => hasExprFormalParamUsage(expr) + case ExprStmt(_, expr) => hasExprFormalParamUsage(expr) case _ => false } - case NestedPathElement(subPath, _) => hasParamUsageAlongPath(Path(subPath.toList), stmts) + case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(Path(subPath.toList), stmts) case _ => false } } @@ -698,19 +687,12 @@ class InterproceduralStringAnalysis( * 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: SEntity, - foundDependees: ListBuffer[(SEntity, Int)], - hasTargetBeenSeen: Boolean - )(implicit tac: TACode[TACMethodParameter, V]): (ListBuffer[(SEntity, Int)], Boolean) = { - var encounteredTarget = false + private def findDependeesAcc(subpath: SubPath, stmts: Array[Stmt[V]], target: SEntity)( + implicit tac: TACode[TACMethodParameter, V], + ): ListBuffer[(SEntity, Int)] = { + val dependees = ListBuffer[(SEntity, Int)]() subpath match { case fpe: FlatPathElement => - if (target.toValueOriginForm(tac.pcToIndex).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 { @@ -720,31 +702,19 @@ class InterproceduralStringAnalysis( param.definedBy.filter(_ >= 0).foreach { ds => val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append(( - outerExpr.asVirtualFunctionCall.params.head.asVar.toPersistentForm(tac.stmts), - fpe.element - )) + dependees.append((param.toPersistentForm(tac.stmts), fpe.element)) } } } case _ => } - (foundDependees, encounteredTarget) + dependees case npe: NestedPathElement => npe.element.foreach { nextSubpath => - if (!encounteredTarget) { - val (_, seen) = findDependeesAcc( - nextSubpath, - stmts, - target, - foundDependees, - encounteredTarget - ) - encounteredTarget = seen - } + dependees.appendAll(findDependeesAcc(nextSubpath, stmts, target)) } - (foundDependees, encounteredTarget) - case _ => (foundDependees, encounteredTarget) + dependees + case _ => dependees } } @@ -758,37 +728,19 @@ class InterproceduralStringAnalysis( * @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: SEntity - )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { + private def findDependentVars(path: Path, stmts: Array[Stmt[V]], ignore: SEntity)( + implicit tac: TACode[TACMethodParameter, V], + ): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() - val ignoreNews = InterpretationHandler.findNewOfVar(ignore.toValueOriginForm(tac.pcToIndex), 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.toValueOriginForm(tac.pcToIndex), stmts) - if (ignore != nextPair._1 && ignoreNews != newExpressions) { - dependees.put(nextPair._1, nextPair._2) - } + findDependeesAcc(nextSubpath, stmts, ignore).foreach { nextPair => + if (ignore != nextPair._1) { + dependees.put(nextPair._1, nextPair._2) } } } dependees } - } object InterproceduralStringAnalysis { 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 7161543a63..260c3669a6 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 @@ -75,9 +75,8 @@ abstract class AbstractStringInterpreter( * second return value indicates whether at least one method has an unknown body (if `true`, * then there is such a method). */ - protected def getMethodsForPC( + protected def getMethodsForPC(pc: Int)( implicit - pc: Int, ps: PropertyStore, callees: Callees, contextProvider: ContextProvider 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 51eaaaeeff..6a929d0ad2 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 @@ -20,7 +20,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result 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 @@ -68,7 +67,7 @@ class InterproceduralInterpretationHandler( ): 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 + val e: Integer = defSite // Function parameters are not evaluated when none are present (this always includes the // implicit parameter for "this" and for exceptions thrown outside the current function) if (defSite < 0 && @@ -187,13 +186,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[New]] expressions. */ private def processNew(expr: New, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val finalEP = new NewInterpreter(cfg, this).interpret( - expr, - defSite - ) - val sci = finalEP.asFinal.p.stringConstancyInformation - state.appendToFpe2Sci(defSite, sci) - state.appendToInterimFpe2Sci(defSite, sci) + val finalEP = new NewInterpreter(cfg, this).interpret(expr, defSite) + state.appendToFpe2Sci(defSite, finalEP.p.stringConstancyInformation) + state.appendToInterimFpe2Sci(defSite, finalEP.p.stringConstancyInformation) finalEP } @@ -205,7 +200,7 @@ class InterproceduralInterpretationHandler( defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new VirtualFunctionCallPreparationInterpreter( + val r = new InterproceduralVirtualFunctionCallInterpreter( cfg, this, ps, @@ -262,7 +257,7 @@ class InterproceduralInterpretationHandler( declaredMethods, contextProvider ).interpret(expr, defSite) - if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } doInterimResultHandling(r, 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 afdd8a9f41..2a83672155 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 @@ -48,7 +48,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val methods = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) + val methods = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) if (methods._1.isEmpty) { // No methods available => Return lower bound return FinalEP(instr, 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 c19d260729..d043509643 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,6 +7,8 @@ package string_analysis package interpretation package interprocedural +import org.opalj.br.ObjectType + import scala.util.Try import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG @@ -53,7 +55,7 @@ class InterproceduralStaticFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - if (instr.declaringClass.fqn == "java/lang/String" && instr.name == "valueOf") { + if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { processStringValueOf(instr) } else { processArbitraryCall(instr, defSite) @@ -98,7 +100,7 @@ class InterproceduralStaticFunctionCallInterpreter( instr: StaticFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) + val methods, _ = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) // Static methods cannot be overwritten, thus // 1) we do not need the second return value of getMethodsForPC and 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/InterproceduralVirtualFunctionCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala index 5ea853ab01..5e581e6ac9 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/InterproceduralVirtualFunctionCallInterpreter.scala @@ -34,7 +34,7 @@ import org.opalj.fpcf.PropertyStore * * @author Patrick Mell */ -class VirtualFunctionCallPreparationInterpreter( +class InterproceduralVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, @@ -58,7 +58,7 @@ class VirtualFunctionCallPreparationInterpreter( *

  • * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see - * [[VirtualFunctionCallPreparationInterpreter.interpretReplaceCall]]. + * [[InterproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. *
  • *
  • * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] @@ -67,12 +67,10 @@ class VirtualFunctionCallPreparationInterpreter( * * * If none of the above-described cases match, a final result containing - * [[StringConstancyProperty.getNeutralElement]] is returned. + * [[StringConstancyProperty.lb]] 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): EOptionP[Entity, StringConstancyProperty] = { @@ -82,11 +80,10 @@ class VirtualFunctionCallPreparationInterpreter( case "replace" => interpretReplaceCall(instr) case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj.fqn == "java/lang/String" => + case obj: ObjectType if obj == ObjectType.String => interpretArbitraryCall(instr, defSite) case _ => - val e: Integer = defSite - FinalEP(e, StringConstancyProperty.lb) + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) } } @@ -103,7 +100,7 @@ class VirtualFunctionCallPreparationInterpreter( * finalized later on. */ private def interpretArbitraryCall(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val (methods, _) = getMethodsForPC(instr.pc, ps, state.callees, contextProvider) + val (methods, _) = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) if (methods.isEmpty) { return FinalEP(instr, StringConstancyProperty.lb) 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 a91422c001..77f296dbfc 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 @@ -40,7 +40,7 @@ class VirtualFunctionCallFinalizer( /** * 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]]. + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralVirtualFunctionCallInterpreter]]. */ private def finalizeAppend(instr: T, defSite: Int): Unit = { val receiverDefSites = instr.receiver.asVar.definedBy.toArray.sorted From 272c2716efa418fdee6f376fa3fabb94af39ce5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 4 Feb 2024 19:37:25 +0100 Subject: [PATCH 347/583] Denote nested switch improvement --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 2 ++ 1 file changed, 2 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 32ede99d7c..7197d47dbd 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 @@ -954,6 +954,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[NestedPathType.SwitchWithDefault]] if the `switch` has a `default` case and * [[NestedPathType.SwitchWithoutDefault]] otherwise. * + * IMPROVE switch bound processing is broken for nested switch statements without statements after them. + * * @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. From ac25c20f29f2cbaa07af45416e5a674dbc1013d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 5 Feb 2024 19:51:22 +0100 Subject: [PATCH 348/583] Refactor to l0 and l1 --- .../info/StringAnalysisReflectiveCalls.scala | 6 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 12 +- .../{common => }/BinaryExprInterpreter.scala | 25 +-- .../{common => }/DoubleValueInterpreter.scala | 24 +-- .../{common => }/FloatValueInterpreter.scala | 25 +-- .../IntegerValueInterpreter.scala | 27 ++-- .../InterpretationHandler.scala | 4 +- .../interpretation/NewInterpreter.scala | 32 ++++ .../StringConstInterpreter.scala | 45 ++++++ ...erpreter.scala => StringInterpreter.scala} | 59 +++----- .../common/NewInterpreter.scala | 42 ----- .../common/StringConstInterpreter.scala | 52 ------- .../IntraproceduralFieldInterpreter.scala | 39 ----- ...ralNonVirtualFunctionCallInterpreter.scala | 38 ----- .../L0StringAnalysis.scala} | 37 ++--- .../interpretation/L0ArrayInterpreter.scala} | 22 +-- .../L0FieldReadInterpreter.scala | 32 ++++ .../L0GetStaticInterpreter.scala} | 14 +- .../L0InterpretationHandler.scala} | 71 ++++----- .../L0NonVirtualMethodCallInterpreter.scala} | 24 +-- .../L0StaticFunctionCallInterpreter.scala} | 19 +-- .../interpretation/L0StringInterpreter.scala | 39 +++++ .../L0VirtualFunctionCallInterpreter.scala} | 22 +-- .../L0VirtualMethodCallInterpreter.scala} | 24 +-- .../L1ComputationState.scala} | 19 +-- .../L1StringAnalysis.scala} | 100 ++++++------ .../finalizer/ArrayLoadFinalizer.scala | 26 ++-- .../finalizer/GetFieldFinalizer.scala | 15 +- .../finalizer/L1Finalizer.scala} | 15 +- .../finalizer/NewArrayFinalizer.scala | 22 +-- .../NonVirtualMethodCallFinalizer.scala | 15 +- .../StaticFunctionCallFinalizer.scala | 15 +- .../VirtualFunctionCallFinalizer.scala | 23 +-- .../L1ArrayAccessInterpreter.scala} | 48 +++--- .../L1FieldReadInterpreter.scala} | 46 +++--- .../L1InterpretationHandler.scala} | 143 ++++++++---------- .../L1NewArrayInterpreter.scala} | 24 ++- ...L1NonVirtualFunctionCallInterpreter.scala} | 38 ++--- .../L1NonVirtualMethodCallInterpreter.scala} | 27 +--- .../L1StaticFunctionCallInterpreter.scala} | 47 ++---- .../interpretation/L1StringInterpreter.scala | 39 +++++ .../L1VirtualFunctionCallInterpreter.scala} | 40 +++-- .../L1VirtualMethodCallInterpreter.scala} | 21 +-- .../preprocessing/PathTransformer.scala | 20 +-- .../string_analysis/string_analysis.scala | 6 +- 45 files changed, 627 insertions(+), 856 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{common => }/BinaryExprInterpreter.scala (61%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{common => }/DoubleValueInterpreter.scala (54%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{common => }/FloatValueInterpreter.scala (54%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{common => }/IntegerValueInterpreter.scala (53%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{AbstractStringInterpreter.scala => StringInterpreter.scala} (74%) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{IntraproceduralStringAnalysis.scala => l0/L0StringAnalysis.scala} (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala => l0/interpretation/L0ArrayInterpreter.scala} (75%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala => l0/interpretation/L0GetStaticInterpreter.scala} (73%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala => l0/interpretation/L0InterpretationHandler.scala} (56%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala => l0/interpretation/L0NonVirtualMethodCallInterpreter.scala} (77%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala => l0/interpretation/L0StaticFunctionCallInterpreter.scala} (60%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala => l0/interpretation/L0VirtualFunctionCallInterpreter.scala} (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala => l0/interpretation/L0VirtualMethodCallInterpreter.scala} (71%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{InterproceduralComputationState.scala => l1/L1ComputationState.scala} (96%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{InterproceduralStringAnalysis.scala => l1/L1StringAnalysis.scala} (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/ArrayLoadFinalizer.scala (61%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/GetFieldFinalizer.scala (66%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/finalizer/AbstractFinalizer.scala => l1/finalizer/L1Finalizer.scala} (81%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/NewArrayFinalizer.scala (50%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/NonVirtualMethodCallFinalizer.scala (74%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/StaticFunctionCallFinalizer.scala (80%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural => l1}/finalizer/VirtualFunctionCallFinalizer.scala (86%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/ArrayPreparationInterpreter.scala => l1/interpretation/L1ArrayAccessInterpreter.scala} (76%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralFieldInterpreter.scala => l1/interpretation/L1FieldReadInterpreter.scala} (83%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralInterpretationHandler.scala => l1/interpretation/L1InterpretationHandler.scala} (76%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/NewArrayPreparer.scala => l1/interpretation/L1NewArrayInterpreter.scala} (85%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala => l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala} (63%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala => l1/interpretation/L1NonVirtualMethodCallInterpreter.scala} (80%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala => l1/interpretation/L1StaticFunctionCallInterpreter.scala} (82%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala => l1/interpretation/L1VirtualFunctionCallInterpreter.scala} (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala => l1/interpretation/L1VirtualMethodCallInterpreter.scala} (72%) 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 52ba6c045b..b1827ba354 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 @@ -40,9 +40,9 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey -import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.SContext +import org.opalj.tac.fpcf.analyses.string_analysis.l0.LazyL0StringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.properties.TACAI /** @@ -310,7 +310,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { project.get(RTACallGraphKey) implicit val (propertyStore, analyses) = manager.runAll( - if (runIntraproceduralAnalysis) LazyIntraproceduralStringAnalysis else LazyInterproceduralStringAnalysis + if (runIntraproceduralAnalysis) LazyL0StringAnalysis else LazyL1StringAnalysis ) // Stores the obtained results for each supported reflective operation 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 57bb52461b..0b534ceecd 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 @@ -14,9 +14,9 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey -import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.SEntity +import org.opalj.tac.fpcf.analyses.string_analysis.l0.LazyL0StringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -101,7 +101,7 @@ object StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch @@ -114,7 +114,7 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/intraprocedural") describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { - val as = executeAnalyses(LazyIntraproceduralStringAnalysis) + val as = executeAnalyses(LazyL0StringAnalysis) val entities = determineEntitiesToAnalyze(as.project) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) @@ -125,7 +125,7 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch @@ -142,7 +142,7 @@ class InterproceduralStringAnalysisTest extends StringAnalysisTest { } describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { - val as = executeAnalyses(LazyInterproceduralStringAnalysis) + val as = executeAnalyses(LazyL1StringAnalysis) val entities = determineEntitiesToAnalyze(as.project) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) 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/BinaryExprInterpreter.scala similarity index 61% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala index 4589abc398..b765adbd51 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/BinaryExprInterpreter.scala @@ -5,7 +5,6 @@ package fpcf package analyses package string_analysis package interpretation -package common import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -15,19 +14,15 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.FinalEP /** - * 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. + * 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 + * @author Maximilian Rüsch */ -class BinaryExprInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class BinaryExprInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { override type T = BinaryExpr[V] @@ -40,12 +35,8 @@ 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, defSite: Int): FinalEP[T, StringConstancyProperty] = { + def interpret(instr: T): FinalEP[T, StringConstancyProperty] = { 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/common/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala similarity index 54% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala index 1e0c9b8797..0e71142d25 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/DoubleValueInterpreter.scala @@ -5,7 +5,6 @@ package fpcf package analyses package string_analysis package interpretation -package common import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -15,27 +14,18 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP /** - * The `DoubleValueInterpreter` is responsible for processing [[DoubleConst]]s. - *

    - * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. + * Responsible for processing [[DoubleConst]]s. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class DoubleValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class DoubleValueInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { override type T = DoubleConst - /** - * @note For this implementation, `defSite` does not play a role. - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + def interpret(instr: T): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( 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/FloatValueInterpreter.scala similarity index 54% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala index 4c0c7b7246..6814054e30 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/FloatValueInterpreter.scala @@ -5,7 +5,6 @@ package fpcf package analyses package string_analysis package interpretation -package common import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -15,27 +14,18 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP /** - * The `FloatValueInterpreter` is responsible for processing [[FloatConst]]s. - *

    - * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. + * Responsible for processing [[FloatConst]]s. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class FloatValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class FloatValueInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { override type T = FloatConst - /** - * @note For this implementation, `defSite` does not play a role. - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + def interpret(instr: T): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( @@ -44,5 +34,4 @@ class FloatValueInterpreter( 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/IntegerValueInterpreter.scala similarity index 53% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala index 23250a6984..2d56a33ebc 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/IntegerValueInterpreter.scala @@ -1,11 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj +package org +package opalj package tac package fpcf package analyses package string_analysis package interpretation -package common import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -15,27 +15,18 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP /** - * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. - *

    - * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. + * Responsible for processing [[IntConst]]s. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class IntegerValueInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class IntegerValueInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { override type T = IntConst - /** - * @note For this implementation, `defSite` does not play a role. - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + def interpret(instr: T): FinalEP[T, StringConstancyProperty] = FinalEP( instr, StringConstancyProperty(StringConstancyInformation( 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 97341fbacd..7a4be2d66d 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 @@ -8,7 +8,6 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.ObjectType import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -17,6 +16,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis import org.opalj.value.ValueInformation abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { @@ -102,7 +102,7 @@ object InterpretationHandler { */ def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = expr.asVar.value.isPrimitiveValue && - InterproceduralStringAnalysis.isSupportedPrimitiveNumberType( + L1StringAnalysis.isSupportedPrimitiveNumberType( expr.asVar.value.asPrimitiveValue.primitiveType.toJava ) 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 new file mode 100644 index 0000000000..ab6e0bb15b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.FinalEP + +/** + * Responsible for processing [[New]] expressions. + * + * @author Maximilian Rüsch + */ +case class NewInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { + + override type T = New + + /** + * [[New]] expressions do not carry any relevant information in this context (as the initial values are not set in + * [[New]] expressions but, e.g., in [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation + * always returns a [[StringConstancyProperty.getNeutralElement]]. + */ + def interpret(instr: T): FinalEP[T, StringConstancyProperty] = + FinalEP(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 new file mode 100644 index 0000000000..a01a880dd8 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package 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.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.fpcf.FinalEP +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts +import org.opalj.tac.V + +/** + * Responsible for processing [[StringConst]]s. + * + * @author Maximilian Rüsch + */ +case class StringConstInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler +) extends StringInterpreter { + + override type T = StringConst + + /** + * Always returns a list with one [[StringConstancyLevel.CONSTANT]] [[StringConstancyInformation]] element holding + * the string const value. + */ + def interpret(instr: T): FinalEP[T, 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/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala similarity index 74% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 260c3669a6..5427ee1c6c 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/StringInterpreter.scala @@ -20,26 +20,30 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation /** - * @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. - * - * @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 + * @author Maximilian Rüsch */ -abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[V], TACStmts[V]], - protected val exprHandler: InterpretationHandler -) { +trait StringInterpreter { + + /** + * The control flow graph that underlies the instruction to interpret. + */ + protected val cfg: CFG[Stmt[V], TACStmts[V]] + + /** + * Handles interpretation of instructions the current interpretation depends on. + * + * @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. + */ + protected val exprHandler: InterpretationHandler type T <: Any @@ -57,7 +61,7 @@ abstract class AbstractStringInterpreter( protected def getTACAI( ps: PropertyStore, m: Method, - s: InterproceduralComputationState + s: L1ComputationState ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { @@ -117,7 +121,7 @@ abstract class AbstractStringInterpreter( /** * evaluateParameters takes a list of parameters, `params`, as produced, e.g., by - * [[AbstractStringInterpreter.getParametersForPCs]], and an interpretation handler, `iHandler` + * [[StringInterpreter.getParametersForPCs]], and an interpretation handler, `iHandler` * 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 / @@ -129,7 +133,7 @@ abstract class AbstractStringInterpreter( */ protected def evaluateParameters( params: List[Seq[Expr[V]]], - iHandler: InterproceduralInterpretationHandler, + iHandler: L1InterpretationHandler, funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] @@ -180,23 +184,4 @@ abstract class AbstractStringInterpreter( StringConstancyInformation.reduceMultiple(param.map { _.p.stringConstancyInformation }) }) }) - - /** - * @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). - *

    - * 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, defSite: Int): EOptionP[Entity, StringConstancyProperty] } 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 deleted file mode 100644 index 6a0a589235..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation -package common - -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FinalEP - -/** - * The `NewInterpreter` is responsible for processing [[New]] expressions. - *

    - * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. - * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell - */ -class NewInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) 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, this implementation always returns a - * Result containing [[StringConstancyProperty.getNeutralElement]]. - * - * @note For this implementation, `defSite` does not play a role. - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): FinalEP[T, 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 deleted file mode 100644 index fad5a16ef3..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation -package common - -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.fpcf.FinalEP - -/** - * 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]] - * - * @author Patrick Mell - */ -class StringConstInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { - - override type T = StringConst - - /** - * The interpretation of a [[StringConst]] always results in a list with one - * [[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, defSite: Int): FinalEP[T, 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/intraprocedural/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala deleted file mode 100644 index 2c9d168bf8..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation -package intraprocedural - -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FinalEP - -/** - * 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 [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] - * is returned (see [[interpret]] of this class). - * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell - */ -class IntraproceduralFieldInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { - - override type T = GetField[V] - - /** - * Fields are not supported by this implementation. Thus, this function always returns - * [[StringConstancyProperty.lb]]. - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty.lb) -} 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 deleted file mode 100644 index e8fe66b1be..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation -package intraprocedural - -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FinalEP - -/** - * The `IntraproceduralNonVirtualFunctionCallInterpreter` is responsible for processing - * [[NonVirtualFunctionCall]]s in an intraprocedural fashion. - * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell - */ -class IntraproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { - - override type T = NonVirtualFunctionCall[V] - - /** - * 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): FinalEP[T, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty.lb) -} 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/l0/L0StringAnalysis.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index d58f30c11a..ea65a4875a 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/l0/L0StringAnalysis.scala @@ -4,9 +4,7 @@ package tac package fpcf package analyses package string_analysis - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +package l0 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject @@ -27,7 +25,7 @@ import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP 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.l0.interpretation.L0InterpretationHandler 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 @@ -37,6 +35,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + /** * 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''. @@ -61,7 +62,7 @@ import org.opalj.value.ValueInformation * * @author Patrick Mell */ -class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalysis { +class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { /** * This class is to be used to store state information that are required at a later point in @@ -139,13 +140,12 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys } } } else { - val interpretationHandler = IntraproceduralInterpretationHandler(tac) - val stringTree = new PathTransformer(interpretationHandler).pathToStringTree(leanPath) + val stringTree = new PathTransformer(L0InterpretationHandler(tac)).pathToStringTree(leanPath) sci = stringTree.reduce(true) } } else { // We deal with pure strings - val interpretationHandler = IntraproceduralInterpretationHandler(tac) + val interpretationHandler = L0InterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => interpretationHandler.processDefSite(ds).p.stringConstancyInformation @@ -175,8 +175,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys state.fpe2sci.put(state.var2IndexMapping(finalEP.e._1), finalEP.p.stringConstancyInformation) if (dependees.isEmpty) { - val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) - val sci = new PathTransformer(interpretationHandler).pathToStringTree( + val sci = new PathTransformer(L0InterpretationHandler(state.tac)).pathToStringTree( state.computedLeanPath, state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } ).reduce(true) @@ -204,8 +203,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys val finalScpEP = finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]] val sciOpt = processFinalP(dependees, state, finalScpEP) if (sciOpt.isDefined) { - val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) - val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( + val finalSci = new PathTransformer(L0InterpretationHandler(state.tac)).pathToStringTree( state.computedLeanPath, state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } ).reduce(true) @@ -291,7 +289,7 @@ class IntraproceduralStringAnalysis(val project: SomeProject) extends FPCFAnalys } } -sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { +sealed trait L0StringAnalysisScheduler extends FPCFAnalysisScheduler { final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) @@ -301,9 +299,9 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule PropertyBounds.lub(StringConstancyProperty) ) - override final type InitializationData = IntraproceduralStringAnalysis + override final type InitializationData = L0StringAnalysis override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new IntraproceduralStringAnalysis(p) + new L0StringAnalysis(p) } override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -317,18 +315,15 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule ): Unit = {} } -/** - * Executor for the lazy analysis. - */ -object LazyIntraproceduralStringAnalysis - extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { +object LazyL0StringAnalysis + extends L0StringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData ): FPCFAnalysis = { - val analysis = new IntraproceduralStringAnalysis(p) + val analysis = new L0StringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis } 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/l0/interpretation/L0ArrayInterpreter.scala similarity index 75% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala index 18729ab2ce..1ae094b059 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/l0/interpretation/L0ArrayInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -13,25 +13,17 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.FinalEP /** - * The `IntraproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as - * [[ArrayStore]] expressions in an intraprocedural fashion. + * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an intraprocedural fashion. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class IntraproceduralArrayInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0ArrayInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = ArrayLoad[V] - /** - * @note For this implementation, `defSite` does not play a role. - * - * @see [[AbstractStringInterpreter.interpret]] - */ override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val stmts = cfg.code.instructions val defSites = instr.arrayRef.asVar.definedBy.toArray diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala new file mode 100644 index 0000000000..34197a5ded --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.FinalEP + +/** + * Responsible for processing [[FieldRead]]s. Currently, there is no support for fields, i.e., they are not analyzed but + * a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned. + * + * @author Maximilian Rüsch + */ +case class L0FieldReadInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { + + override type T = GetField[V] + + /** + * Fields are currently unsupported, thus this function always returns [[StringConstancyProperty.lb]]. + */ + override def interpret(instr: T, defSite: Int): FinalEP[T, 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/l0/interpretation/L0GetStaticInterpreter.scala similarity index 73% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala index cefcde8f27..8775bf7500 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/l0/interpretation/L0GetStaticInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -17,21 +17,17 @@ import org.opalj.fpcf.FinalEP * fixed [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned * (see [[interpret]]). * - * @see [[AbstractStringInterpreter]] - * * @author Patrick Mell */ -class IntraproceduralGetStaticInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0GetStaticInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = GetStatic /** * Currently, this type is not interpreted. Thus, this function always returns [[StringConstancyProperty.lb]]. - * - * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) 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/l0/interpretation/L0InterpretationHandler.scala similarity index 56% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index d1937a1228..deaa3be349 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/l0/interpretation/L0InterpretationHandler.scala @@ -4,34 +4,33 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP -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.common.NewInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter -import org.opalj.value.ValueInformation +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter /** * `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 - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) return - * a final computation result! + * This handler may use [[L0StringInterpreter]]s and general + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter]]s. * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class IntraproceduralInterpretationHandler( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] +class L0InterpretationHandler( + tac: TACode[TACMethodParameter, V] ) extends InterpretationHandler(tac) { /** @@ -56,45 +55,41 @@ class IntraproceduralInterpretationHandler( stmts(defSite) match { case Assignment(_, _, expr: StringConst) => - new StringConstInterpreter(cfg, this).interpret(expr, defSite) + StringConstInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: IntConst) => - new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) + IntegerValueInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: FloatConst) => - new FloatValueInterpreter(cfg, this).interpret(expr, defSite) + FloatValueInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: DoubleConst) => - new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) + DoubleValueInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: BinaryExpr[V]) => + BinaryExprInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: ArrayLoad[V]) => - new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) + L0ArrayInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: New) => - new NewInterpreter(cfg, this).interpret(expr, defSite) + NewInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: GetField[V]) => + L0FieldReadInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => - new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) => - new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - new IntraproceduralNonVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: GetField[V]) => - new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) + // Currently unsupported + FinalEP(expr, StringConstancyProperty.lb) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) => - new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + case ExprStmt(_, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case vmc: VirtualMethodCall[V] => - new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) + L0VirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => - new IntraproceduralNonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc, defSite) + L0NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc, defSite) case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } } } -object IntraproceduralInterpretationHandler { +object L0InterpretationHandler { - /** - * @see [[IntraproceduralInterpretationHandler]] - */ - def apply(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]): IntraproceduralInterpretationHandler = - new IntraproceduralInterpretationHandler(tac) + def apply(tac: TACode[TACMethodParameter, V]): L0InterpretationHandler = new L0InterpretationHandler(tac) } 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/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala similarity index 77% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index f535369095..dc201763d6 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/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -13,18 +13,15 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.FinalEP /** - * The `IntraproceduralNonVirtualMethodCallInterpreter` is responsible for processing - * [[NonVirtualMethodCall]]s in an intraprocedural fashion. + * 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 + * @author Maximilian Rüsch */ -class IntraproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0NonVirtualMethodCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = NonVirtualMethodCall[V] @@ -38,12 +35,7 @@ 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]] + * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. */ override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val prop = instr.name match { 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/l0/interpretation/L0StaticFunctionCallInterpreter.scala similarity index 60% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index a931b7cfbb..53c96faee2 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/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -17,22 +17,15 @@ import org.opalj.fpcf.FinalEP *

    * For supported method calls, see the documentation of the `interpret` function. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class IntraproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0StaticFunctionCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = StaticFunctionCall[V] - /** - * This function always returns a result containing [[StringConstancyProperty.lb]]. - * - * @see [[AbstractStringInterpreter.interpret]] - */ override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala new file mode 100644 index 0000000000..66808a0965 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity +import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter + +/** + * @author Maximilian Rüsch + */ +trait L0StringInterpreter extends StringInterpreter { + + override protected val exprHandler: L0InterpretationHandler + + /** + * @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). + *

    + * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int): FinalEP[Entity, StringConstancyProperty] +} 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/l0/interpretation/L0VirtualFunctionCallInterpreter.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index e437d5151c..955041a8d9 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/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.ComputationalTypeDouble import org.opalj.br.ComputationalTypeFloat @@ -19,20 +19,18 @@ 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.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * The `IntraproceduralVirtualFunctionCallInterpreter` is responsible for processing - * [[VirtualFunctionCall]]s in an intraprocedural fashion. + * 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 IntraproceduralVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0VirtualFunctionCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = VirtualFunctionCall[V] @@ -48,7 +46,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( *

  • * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see - * [[IntraproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. + * [[L0VirtualFunctionCallInterpreter.interpretReplaceCall]]. *
  • *
  • * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] @@ -58,10 +56,6 @@ 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, defSite: Int): FinalEP[T, StringConstancyProperty] = { val property = instr.name match { 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/l0/interpretation/L0VirtualMethodCallInterpreter.scala similarity index 71% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 0773248488..7a209163ff 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/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -4,8 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l0 package interpretation -package intraprocedural import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -15,18 +15,15 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP /** - * The `IntraproceduralVirtualMethodCallInterpreter` is responsible for processing - * [[VirtualMethodCall]]s in an intraprocedural fashion. + * 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 + * @author Maximilian Rüsch */ -class IntraproceduralVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L0VirtualMethodCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler +) extends L0StringInterpreter { override type T = VirtualMethodCall[V] @@ -41,12 +38,7 @@ 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]] + * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. */ override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { 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/l1/L1ComputationState.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index eb22169b95..f81e55eaf3 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/l1/L1ComputationState.scala @@ -4,22 +4,23 @@ package tac package fpcf package analyses package string_analysis - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +package l1 import org.opalj.br.DeclaredMethod import org.opalj.br.Method import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity import org.opalj.fpcf.Property -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.value.ValueInformation +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + /** * 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 @@ -27,9 +28,9 @@ import org.opalj.value.ValueInformation * * @param entity The entity for which the analysis was started with. * @param fieldWriteThreshold See the documentation of - * [[InterproceduralStringAnalysis#fieldWriteThreshold]]. + * [[L1StringAnalysis#fieldWriteThreshold]]. */ -case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, fieldWriteThreshold: Int = 100) { +case class L1ComputationState(dm: DeclaredMethod, entity: SContext, fieldWriteThreshold: Int = 100) { /** * The Three-Address Code of the entity's method */ @@ -38,14 +39,14 @@ case class InterproceduralComputationState(dm: DeclaredMethod, entity: SContext, /** * The interpretation handler to use for computing a final result (if possible). */ - var iHandler: InterproceduralInterpretationHandler = _ + var iHandler: L1InterpretationHandler = _ /** * 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 = _ + var interimIHandler: L1InterpretationHandler = _ /** * 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/l1/L1StringAnalysis.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 0a25e3cb96..164f59fe66 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/l1/L1StringAnalysis.scala @@ -4,9 +4,8 @@ package tac package fpcf package analyses package string_analysis +package l1 -import scala.collection.mutable -import scala.collection.mutable.ListBuffer import org.opalj.br.FieldType import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey @@ -34,8 +33,8 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler 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 @@ -47,17 +46,20 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + /** * 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 [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]], 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]]) + * description of [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]] 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 [[L1InterpretationHandler]]) * 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 @@ -72,9 +74,7 @@ import org.opalj.value.ValueInformation * * @author Patrick Mell */ -class InterproceduralStringAnalysis( - val project: SomeProject -) extends FPCFAnalysis { +class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { private val contextProvider: ContextProvider = project.get(ContextProviderKey) @@ -105,7 +105,7 @@ class InterproceduralStringAnalysis( * bounds can be used for the interim result. */ private def getInterimResult( - state: InterproceduralComputationState + state: L1ComputationState ): InterimResult[StringConstancyProperty] = InterimResult( state.entity, computeNewLowerBound(state), @@ -115,7 +115,7 @@ class InterproceduralStringAnalysis( ) private def computeNewUpperBound( - state: InterproceduralComputationState + state: L1ComputationState ): StringConstancyProperty = { if (state.computedLeanPath != null) { StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( @@ -128,12 +128,12 @@ class InterproceduralStringAnalysis( } private def computeNewLowerBound( - state: InterproceduralComputationState + state: L1ComputationState ): StringConstancyProperty = StringConstancyProperty.lb def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) - val state = InterproceduralComputationState(dm, data, fieldWriteThreshold) + val state = l1.L1ComputationState(dm, data, fieldWriteThreshold) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { @@ -162,7 +162,7 @@ class InterproceduralStringAnalysis( * 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(state: InterproceduralComputationState): ProperPropertyComputationResult = { + private def determinePossibleStrings(state: L1ComputationState): ProperPropertyComputationResult = { val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted @@ -178,10 +178,9 @@ class InterproceduralStringAnalysis( } if (state.iHandler == null) { - state.iHandler = InterproceduralInterpretationHandler( + state.iHandler = L1InterpretationHandler( state.tac, ps, - declaredMethods, declaredFields, fieldAccessInformation, state, @@ -193,10 +192,9 @@ class InterproceduralStringAnalysis( interimState.callees = state.callees interimState.callers = state.callers interimState.params = state.params - state.interimIHandler = InterproceduralInterpretationHandler( + state.interimIHandler = L1InterpretationHandler( state.tac, ps, - declaredMethods, declaredFields, fieldAccessInformation, interimState, @@ -206,7 +204,7 @@ class InterproceduralStringAnalysis( var requiresCallersInfo = false if (state.params.isEmpty) { - state.params = InterproceduralStringAnalysis.getParams(state.entity) + state.params = L1StringAnalysis.getParams(state.entity) } if (state.params.isEmpty) { // In case a parameter is required for approximating a string, retrieve callers information @@ -215,9 +213,9 @@ class InterproceduralStringAnalysis( requiresCallersInfo = if (defSites.exists(_ < 0)) { if (InterpretationHandler.isStringConstExpression(uVar)) { hasCallersOrParamInfo - } else if (InterproceduralStringAnalysis.isSupportedPrimitiveNumberType(uVar)) { + } else if (L1StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { val numType = uVar.value.asPrimitiveValue.primitiveType.toJava - val sci = InterproceduralStringAnalysis.getDynamicStringInformationForNumberType(numType) + val sci = L1StringAnalysis.getDynamicStringInformationForNumberType(numType) return Result(state.entity, StringConstancyProperty(sci)) } else { // StringBuilders as parameters are currently not evaluated @@ -231,7 +229,7 @@ class InterproceduralStringAnalysis( return Result(state.entity, StringConstancyProperty.lb) } val hasSupportedParamType = state.entity._2.parameterTypes.exists { - InterproceduralStringAnalysis.isSupportedType + L1StringAnalysis.isSupportedType } if (hasSupportedParamType) { hasFormalParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) @@ -323,7 +321,7 @@ class InterproceduralStringAnalysis( if (state.dependees.nonEmpty) { getInterimResult(state) } else { - InterproceduralStringAnalysis.unregisterParams(state.entity) + L1StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(sci)) } } @@ -338,7 +336,7 @@ class InterproceduralStringAnalysis( * be returned. */ private def continuation( - state: InterproceduralComputationState + state: L1ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != eps.e) @@ -439,9 +437,9 @@ class InterproceduralStringAnalysis( } private def finalizePreparations( - path: Path, - state: InterproceduralComputationState, - iHandler: InterproceduralInterpretationHandler + path: Path, + state: L1ComputationState, + iHandler: L1InterpretationHandler ): Unit = path.elements.foreach { case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { @@ -464,21 +462,21 @@ class InterproceduralStringAnalysis( * not have been called)! * @return Returns the final result. */ - private def computeFinalResult(state: InterproceduralComputationState): Result = { + private def computeFinalResult(state: L1ComputationState): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci, resetExprHandler = false ).reduce(true) - InterproceduralStringAnalysis.unregisterParams(state.entity) + L1StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(finalSci)) } private def processFinalP( - state: InterproceduralComputationState, - e: Entity, - p: StringConstancyProperty + state: L1ComputationState, + e: Entity, + p: StringConstancyProperty ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { @@ -497,11 +495,11 @@ 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]]. + * interpretations are registered using [[L1StringAnalysis.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 = { + private def registerParams(state: L1ComputationState): Boolean = { val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq if (callers.length > callersThreshold) { state.params.append( @@ -534,7 +532,7 @@ class InterproceduralStringAnalysis( ))) } // Recursively analyze supported types - if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { + if (L1StringAnalysis.isSupportedType(p.asVar)) { val paramEntity = (p.asVar.toPersistentForm(state.tac.stmts), m.definedMethod) val eps = propertyStore(paramEntity, StringConstancyProperty.key) state.appendToVar2IndexMapping(paramEntity._1, paramIndex) @@ -556,7 +554,7 @@ class InterproceduralStringAnalysis( } // If all parameters could already be determined, register them if (!hasIntermediateResult) { - InterproceduralStringAnalysis.registerParams(state.entity, state.params) + L1StringAnalysis.registerParams(state.entity, state.params) } !hasIntermediateResult } @@ -567,12 +565,12 @@ class InterproceduralStringAnalysis( * * @param p The path to traverse. * @param state The current state of the computation. This function will alter - * [[InterproceduralComputationState.fpe2sci]]. + * [[L1ComputationState.fpe2sci]]. * @return Returns `true` if all values computed for the path are final results. */ private def computeResultsForPath( p: Path, - state: InterproceduralComputationState + state: L1ComputationState ): Boolean = { var hasFinalResult = true @@ -663,7 +661,7 @@ class InterproceduralStringAnalysis( private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { def hasExprFormalParamUsage(expr: Expr[V]): Boolean = expr match { - case al: ArrayLoad[V] => ArrayPreparationInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) + case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) case duVar: V => duVar.definedBy.exists(_ < 0) case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) @@ -743,7 +741,7 @@ class InterproceduralStringAnalysis( } } -object InterproceduralStringAnalysis { +object L1StringAnalysis { /** * Maps entities to a list of lists of parameters. As currently this analysis works context- @@ -802,7 +800,7 @@ object InterproceduralStringAnalysis { * * @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)]]. + * see [[L1StringAnalysis.isSupportedType(String)]]. */ def isSupportedType(v: V): Boolean = if (v.value.isPrimitiveValue) { @@ -820,7 +818,7 @@ object InterproceduralStringAnalysis { * * @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)]]. + * see [[L1StringAnalysis.isSupportedType(String)]]. */ def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) @@ -842,7 +840,7 @@ object InterproceduralStringAnalysis { } } -sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { +sealed trait L1StringAnalysisScheduler extends FPCFAnalysisScheduler { final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) @@ -852,9 +850,9 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule PropertyBounds.lub(StringConstancyProperty) ) - override final type InitializationData = InterproceduralStringAnalysis + override final type InitializationData = L1StringAnalysis override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new InterproceduralStringAnalysis(p) + new L1StringAnalysis(p) } override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -867,11 +865,11 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule /** * Executor for the lazy analysis. */ -object LazyInterproceduralStringAnalysis - extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { +object LazyL1StringAnalysis + extends L1StringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register(p: SomeProject, ps: PropertyStore, analysis: InitializationData): FPCFAnalysis = { - val analysis = new InterproceduralStringAnalysis(p) + val analysis = new L1StringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala similarity index 61% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala index 709d1a2315..613bcb6f7a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala @@ -4,22 +4,20 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package 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.l1.interpretation.L1ArrayAccessInterpreter + +import scala.collection.mutable.ListBuffer /** - * @author Patrick Mell + * @author Maximilian Rüsch */ -class ArrayLoadFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] -) extends AbstractFinalizer(state) { +case class ArrayLoadFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override type T = ArrayLoad[V] @@ -29,7 +27,7 @@ class ArrayLoadFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) + val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) allDefSites.foreach { ds => if (!state.fpe2sci.contains(ds)) { @@ -42,9 +40,3 @@ class ArrayLoadFinalizer( )) } } - -object ArrayLoadFinalizer { - - def apply(state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]]): ArrayLoadFinalizer = - new ArrayLoadFinalizer(state, cfg) -} 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/l1/finalizer/GetFieldFinalizer.scala similarity index 66% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala index a7c17b885e..6021b24a6a 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/l1/finalizer/GetFieldFinalizer.scala @@ -4,11 +4,15 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package finalizer -class GetFieldFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { +/** + * @author Maximilian Rüsch + */ +case class GetFieldFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override protected type T = FieldRead[V] @@ -22,8 +26,3 @@ class GetFieldFinalizer(state: InterproceduralComputationState) extends Abstract // called after all dependencies are resolved. state.iHandler.processDefSite(defSite) } - -object GetFieldFinalizer { - - def apply(state: InterproceduralComputationState): GetFieldFinalizer = new GetFieldFinalizer(state) -} 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/l1/finalizer/L1Finalizer.scala similarity index 81% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala index f5d9820452..e78da63ccd 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/l1/finalizer/L1Finalizer.scala @@ -4,8 +4,7 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package finalizer /** @@ -14,15 +13,17 @@ package finalizer * 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`, + * Implementations of this trait 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: InterproceduralComputationState) { +trait L1Finalizer { + + /** + * The computation state to use to retrieve partial results and to write the final result back. + */ + protected val state: L1ComputationState protected type T <: Any 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/l1/finalizer/NewArrayFinalizer.scala similarity index 50% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala index ce11422ae1..10bded6ea7 100644 --- 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/l1/finalizer/NewArrayFinalizer.scala @@ -4,19 +4,15 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package finalizer -import org.opalj.br.cfg.CFG - /** - * @author Patrick Mell + * @author Maximilian Rüsch */ -class NewArrayFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] -) extends AbstractFinalizer(state) { +case class NewArrayFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override type T = NewArray[V] @@ -29,11 +25,3 @@ class NewArrayFinalizer( // 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) -} 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/l1/finalizer/NonVirtualMethodCallFinalizer.scala similarity index 74% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala index f285cdceda..778b88fafd 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/l1/finalizer/NonVirtualMethodCallFinalizer.scala @@ -4,16 +4,17 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package finalizer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** - * @author Patrick Mell + * @author Maximilian Rüsch */ -class NonVirtualMethodCallFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { +case class NonVirtualMethodCallFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override type T = NonVirtualMethodCall[V] @@ -37,9 +38,3 @@ class NonVirtualMethodCallFinalizer(state: InterproceduralComputationState) exte state.appendToFpe2Sci(defSite, toAppend, reset = true) } } - -object NonVirtualMethodCallFinalizer { - - def apply(state: InterproceduralComputationState): NonVirtualMethodCallFinalizer = - new NonVirtualMethodCallFinalizer(state) -} 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/l1/finalizer/StaticFunctionCallFinalizer.scala similarity index 80% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala index 8c1c611ab4..63f734ae69 100644 --- 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/l1/finalizer/StaticFunctionCallFinalizer.scala @@ -4,16 +4,17 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package finalizer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** - * @author Patrick Mell + * @author Maximilian Rüsch */ -class StaticFunctionCallFinalizer(state: InterproceduralComputationState) extends AbstractFinalizer(state) { +case class StaticFunctionCallFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override type T = StaticFunctionCall[V] @@ -43,9 +44,3 @@ class StaticFunctionCallFinalizer(state: InterproceduralComputationState) extend state.appendToFpe2Sci(defSite, toAppend, reset = true) } } - -object StaticFunctionCallFinalizer { - - def apply(state: InterproceduralComputationState): StaticFunctionCallFinalizer = - new StaticFunctionCallFinalizer(state) -} 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/l1/finalizer/VirtualFunctionCallFinalizer.scala similarity index 86% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala index 77f296dbfc..0c531cbe8a 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/l1/finalizer/VirtualFunctionCallFinalizer.scala @@ -4,22 +4,19 @@ package tac package fpcf package analyses package string_analysis -package interpretation -package interprocedural +package l1 package 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 /** - * @author Patrick Mell + * @author Maximilian Rüsch */ -class VirtualFunctionCallFinalizer( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] -) extends AbstractFinalizer(state) { +case class VirtualFunctionCallFinalizer( + override protected val state: L1ComputationState +) extends L1Finalizer { override type T = VirtualFunctionCall[V] @@ -40,7 +37,7 @@ class VirtualFunctionCallFinalizer( /** * 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.InterproceduralVirtualFunctionCallInterpreter]]. + * [[org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1VirtualFunctionCallInterpreter]]. */ private def finalizeAppend(instr: T, defSite: Int): Unit = { val receiverDefSites = instr.receiver.asVar.definedBy.toArray.sorted @@ -105,11 +102,3 @@ class VirtualFunctionCallFinalizer( state.appendToFpe2Sci(defSite, finalSci) } } - -object VirtualFunctionCallFinalizer { - - def apply( - state: InterproceduralComputationState, - cfg: CFG[Stmt[V], TACStmts[V]] - ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) -} 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/l1/interpretation/L1ArrayAccessInterpreter.scala similarity index 76% 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/l1/interpretation/L1ArrayAccessInterpreter.scala index d26047b84d..c911f0148e 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/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -4,55 +4,55 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural - -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.collection.immutable.IntTrieSet -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP +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.V +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState + +import scala.collection.mutable.ListBuffer /** - * The `ArrayPreparationInterpreter` is responsible for preparing [[ArrayLoad]] as well as - * [[ArrayStore]] expressions in an interprocedural fashion. + * 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. + * 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: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]] -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L1ArrayAccessInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]] +) extends L1StringInterpreter { override type T = ArrayLoad[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 + * definition sites producing a refinable 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] = { val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) + val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, ep) => if (ep.isFinal) @@ -95,7 +95,7 @@ class ArrayPreparationInterpreter( } -object ArrayPreparationInterpreter { +object L1ArrayAccessInterpreter { type T = ArrayLoad[V] 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/l1/interpretation/L1FieldReadInterpreter.scala similarity index 83% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 504dbca159..11813a8bbe 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/l1/interpretation/L1FieldReadInterpreter.scala @@ -4,54 +4,52 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural - -import scala.collection.mutable.ListBuffer import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState + +import scala.collection.mutable.ListBuffer /** - * 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]]. + * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields + * via the [[FieldAccessInformation]]. * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class InterproceduralFieldInterpreter( - state: InterproceduralComputationState, - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, - implicit val declaredFields: DeclaredFields, - implicit val contextProvider: ContextProvider -) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { +case class L1FieldReadInterpreter( + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + implicit val declaredFields: DeclaredFields, + implicit val contextProvider: ContextProvider +) extends L1StringInterpreter { + + override protected val cfg: CFG[Stmt[V], TACStmts[V]] = state.tac.cfg override type T = FieldRead[V] /** * Currently, fields are approximated using the following approach. If a field of a type not - * supported by the [[InterproceduralStringAnalysis]] is passed, + * supported by the [[L1StringAnalysis]] 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! - * - * @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, @@ -59,7 +57,7 @@ class InterproceduralFieldInterpreter( // String analysis could then use the field analysis. val defSitEntity: Integer = defSite // Unknown type => Cannot further approximate - if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { + if (!L1StringAnalysis.isSupportedType(instr.declaredFieldType)) { return FinalEP(instr, StringConstancyProperty.lb) } // Write accesses exceeds the threshold => approximate with lower bound 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/l1/interpretation/L1InterpretationHandler.scala similarity index 76% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 6a929d0ad2..917349b61f 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/l1/interpretation/L1InterpretationHandler.scala @@ -4,55 +4,51 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.DeclaredFields -import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -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.common.NewInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.ArrayLoadFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.GetFieldFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NewArrayFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NonVirtualMethodCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.GetFieldFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.StaticFunctionCallFinalizer import org.opalj.value.ValueInformation /** - * `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. + * 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 - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) can - * either return a final or intermediate result. + * This handler may use [[L1StringInterpreter]]s and general + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter]]s. * * @author Patrick Mell */ -class InterproceduralInterpretationHandler( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredMethods: DeclaredMethods, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState, - contextProvider: ContextProvider +class L1InterpretationHandler( + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + state: L1ComputationState, + contextProvider: ContextProvider ) extends InterpretationHandler(tac) { /** @@ -86,7 +82,6 @@ class InterproceduralInterpretationHandler( // Note that def sites referring to constant expressions will be deleted further down processedDefSites(defSite) = () - val callees = state.callees stmts(defSite) match { case Assignment(_, _, expr: StringConst) => processConstExpr(expr, defSite) case Assignment(_, _, expr: IntConst) => processConstExpr(expr, defSite) @@ -104,7 +99,7 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) - case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite, callees) + case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) @@ -119,12 +114,12 @@ class InterproceduralInterpretationHandler( private def processConstExpr( constExpr: SimpleValueConst, defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + ): FinalEP[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) - case sc: StringConst => new StringConstInterpreter(cfg, this).interpret(sc, defSite) + case ic: IntConst => IntegerValueInterpreter(cfg, this).interpret(ic) + case fc: FloatConst => FloatValueInterpreter(cfg, this).interpret(fc) + case dc: DoubleConst => DoubleValueInterpreter(cfg, this).interpret(dc) + case sc: StringConst => StringConstInterpreter(cfg, this).interpret(sc) case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } val sci = finalEP.p.stringConstancyInformation @@ -142,7 +137,7 @@ class InterproceduralInterpretationHandler( defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new ArrayPreparationInterpreter( + val r = new L1ArrayAccessInterpreter( cfg, this, state, @@ -166,7 +161,7 @@ class InterproceduralInterpretationHandler( defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new NewArrayPreparer( + val r = new L1NewArrayInterpreter( cfg, this, state, @@ -185,8 +180,8 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[New]] expressions. */ - private def processNew(expr: New, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val finalEP = new NewInterpreter(cfg, this).interpret(expr, defSite) + private def processNew(expr: New, defSite: Int): FinalEP[Entity, StringConstancyProperty] = { + val finalEP = NewInterpreter(cfg, this).interpret(expr) state.appendToFpe2Sci(defSite, finalEP.p.stringConstancyInformation) state.appendToInterimFpe2Sci(defSite, finalEP.p.stringConstancyInformation) finalEP @@ -200,12 +195,11 @@ class InterproceduralInterpretationHandler( defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralVirtualFunctionCallInterpreter( + val r = new L1VirtualFunctionCallInterpreter( cfg, this, ps, state, - declaredMethods, params, contextProvider ).interpret(expr, defSite) @@ -248,13 +242,12 @@ class InterproceduralInterpretationHandler( defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralStaticFunctionCallInterpreter( + val r = new L1StaticFunctionCallInterpreter( cfg, this, ps, state, params, - declaredMethods, contextProvider ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { @@ -268,11 +261,10 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[BinaryExpr]]s. */ - 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 + private def processBinaryExpr(expr: BinaryExpr[V], defSite: Int): FinalEP[Entity, StringConstancyProperty] = { + // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions + val result = BinaryExprInterpreter(cfg, this).interpret(expr) + val sci = result.p.stringConstancyInformation state.appendToInterimFpe2Sci(defSite, sci) state.appendToFpe2Sci(defSite, sci) result @@ -282,9 +274,9 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField(expr: FieldRead[V], defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralFieldInterpreter( - state, + val r = L1FieldReadInterpreter( this, + state, ps, fieldAccessInformation, declaredFields, @@ -304,12 +296,11 @@ class InterproceduralInterpretationHandler( expr: NonVirtualFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralNonVirtualFunctionCallInterpreter( + val r = L1NonVirtualFunctionCallInterpreter( cfg, this, ps, state, - declaredMethods, contextProvider ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { @@ -324,14 +315,9 @@ class InterproceduralInterpretationHandler( */ def processVirtualMethodCall( expr: VirtualMethodCall[V], - defSite: Int, - callees: Callees + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralVirtualMethodCallInterpreter( - cfg, - this, - callees - ).interpret(expr, defSite) + val r = L1VirtualMethodCallInterpreter(cfg, this).interpret(expr, defSite) doInterimResultHandling(r, defSite) r } @@ -343,8 +329,7 @@ class InterproceduralInterpretationHandler( nvmc: NonVirtualMethodCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { - val r = new InterproceduralNonVirtualMethodCallInterpreter(cfg, this, ps, state, declaredMethods) - .interpret(nvmc, defSite) + val r = L1NonVirtualMethodCallInterpreter(cfg, this, state).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => state.appendToInterimFpe2Sci(defSite, p.stringConstancyInformation) @@ -387,7 +372,7 @@ class InterproceduralInterpretationHandler( /** * Finalized a given definition state. */ - def finalizeDefSite(defSite: Int, state: InterproceduralComputationState): Unit = { + def finalizeDefSite(defSite: Int, state: L1ComputationState): Unit = { if (defSite < 0) { state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) } else { @@ -395,13 +380,13 @@ class InterproceduralInterpretationHandler( case nvmc: NonVirtualMethodCall[V] => NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) case Assignment(_, _, al: ArrayLoad[V]) => - ArrayLoadFinalizer(state, cfg).finalizeInterpretation(al, defSite) + ArrayLoadFinalizer(state).finalizeInterpretation(al, defSite) case Assignment(_, _, na: NewArray[V]) => - NewArrayFinalizer(state, cfg).finalizeInterpretation(na, defSite) + NewArrayFinalizer(state).finalizeInterpretation(na, defSite) case Assignment(_, _, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + VirtualFunctionCallFinalizer(state).finalizeInterpretation(vfc, defSite) case ExprStmt(_, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + VirtualFunctionCallFinalizer(state).finalizeInterpretation(vfc, defSite) case Assignment(_, _, fr: FieldRead[V]) => GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) case ExprStmt(_, fr: FieldRead[V]) => @@ -415,26 +400,20 @@ class InterproceduralInterpretationHandler( } } } - } -object InterproceduralInterpretationHandler { +object L1InterpretationHandler { - /** - * @see [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] - */ def apply( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredMethods: DeclaredMethods, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState, - contextProvider: ContextProvider - ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + state: L1ComputationState, + contextProvider: ContextProvider + ): L1InterpretationHandler = new L1InterpretationHandler( tac, ps, - declaredMethods, declaredFields, fieldAccessInformation, state, 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/l1/interpretation/L1NewArrayInterpreter.scala similarity index 85% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 3754458ec8..d8339ce2c6 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/l1/interpretation/L1NewArrayInterpreter.scala @@ -4,14 +4,14 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural 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.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP /** @@ -22,28 +22,22 @@ import org.opalj.fpcf.FinalEP * 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) { +class L1NewArrayInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]] +) extends L1StringInterpreter { 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 + * definition sites producing a refinable 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 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/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala similarity index 63% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 2a83672155..0dc3a39aad 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/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -4,49 +4,33 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural -import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore /** - * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing - * [[NonVirtualFunctionCall]]s in an interprocedural fashion. - * - * @see [[AbstractStringInterpreter]] + * Responsible for processing [[NonVirtualFunctionCall]]s in an interprocedural fashion. * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - contextProvider: ContextProvider -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L1NonVirtualFunctionCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + contextProvider: ContextProvider +) extends L1StringInterpreter { override type T = NonVirtualFunctionCall[V] - /** - * Currently, [[NonVirtualFunctionCall]]s are not supported. 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]]. - * - * @note For this implementation, `defSite` plays a role! - * - * @see [[AbstractStringInterpreter.interpret]] - */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val methods = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) if (methods._1.isEmpty) { 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/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala similarity index 80% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 6a2b7d9c05..88b2862537 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/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -4,34 +4,27 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural -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.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.PropertyStore /** - * The `InterproceduralNonVirtualMethodCallInterpreter` is responsible for processing - * [[NonVirtualMethodCall]]s in an interprocedural fashion. + * 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, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods -) extends AbstractStringInterpreter(cfg, exprHandler) { +case class L1NonVirtualMethodCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState +) extends L1StringInterpreter { override type T = NonVirtualMethodCall[V] @@ -45,10 +38,6 @@ 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: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { 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/l1/interpretation/L1StaticFunctionCallInterpreter.scala similarity index 82% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index d043509643..d26868cfa7 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/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -4,56 +4,40 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural import org.opalj.br.ObjectType - -import scala.util.Try -import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import scala.util.Try + /** - * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing - * [[StaticFunctionCall]]s in an interprocedural fashion. - *

    + * 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, - ps: PropertyStore, - state: InterproceduralComputationState, - params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods, - contextProvider: ContextProvider -) extends AbstractStringInterpreter(cfg, exprHandler) { +class L1StaticFunctionCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]], + contextProvider: ContextProvider +) extends L1StringInterpreter { override type T = StaticFunctionCall[V] - /** - * 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]]. - * - * @note For this implementation, `defSite` plays a role! - * - * @see [[AbstractStringInterpreter.interpret]] - */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { processStringValueOf(instr) @@ -93,9 +77,6 @@ class InterproceduralStaticFunctionCallInterpreter( } } - /** - * This function interprets an arbitrary static function call. - */ private def processArbitraryCall( instr: StaticFunctionCall[V], defSite: Int @@ -166,7 +147,7 @@ class InterproceduralStaticFunctionCallInterpreter( } else { val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), m) - InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) + L1StringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala new file mode 100644 index 0000000000..44e1e4449d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l1 +package interpretation + +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Entity +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter + +/** + * @author Maximilian Rüsch + */ +trait L1StringInterpreter extends StringInterpreter { + + override protected val exprHandler: L1InterpretationHandler + + /** + * @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). + *

    + * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int): EOptionP[Entity, StringConstancyProperty] +} 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/l1/interpretation/L1VirtualFunctionCallInterpreter.scala similarity index 93% 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/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 5e581e6ac9..96bc9e87ea 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/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -4,13 +4,12 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural 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.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext @@ -18,31 +17,28 @@ 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing - * [[VirtualFunctionCall]]s in an interprocedural fashion. + * 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, - ps: PropertyStore, - state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]], - contextProvider: ContextProvider -) extends AbstractStringInterpreter(cfg, exprHandler) { +class L1VirtualFunctionCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]], + contextProvider: ContextProvider +) extends L1StringInterpreter { override type T = VirtualFunctionCall[V] @@ -58,7 +54,7 @@ class InterproceduralVirtualFunctionCallInterpreter( *

  • * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see - * [[InterproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. + * [[L1VirtualFunctionCallInterpreter.interpretReplaceCall]]. *
  • *
  • * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] @@ -69,9 +65,7 @@ class InterproceduralVirtualFunctionCallInterpreter( * If none of the above-described cases match, a final result containing * [[StringConstancyProperty.lb]] 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): EOptionP[Entity, StringConstancyProperty] = { val result = instr.name match { @@ -153,7 +147,7 @@ class InterproceduralVirtualFunctionCallInterpreter( val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) - InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) + L1StringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { case r: FinalEP[SContext, StringConstancyProperty] => @@ -248,7 +242,7 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V], - state: InterproceduralComputationState + state: L1ComputationState ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -279,7 +273,7 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V], - state: InterproceduralComputationState + state: L1ComputationState ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar @@ -362,7 +356,7 @@ class InterproceduralVirtualFunctionCallInterpreter( * (Currently, this function simply approximates `replace` functions by returning the lower * bound of [[StringConstancyProperty]]). */ - private def interpretReplaceCall(instr: VirtualFunctionCall[V]): EOptionP[Entity, StringConstancyProperty] = + private def interpretReplaceCall(instr: VirtualFunctionCall[V]): FinalEP[Entity, StringConstancyProperty] = FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) /** 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/l1/interpretation/L1VirtualMethodCallInterpreter.scala similarity index 72% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala index f371048cd1..4fb214f0c4 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/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -4,31 +4,26 @@ package tac package fpcf package analyses package string_analysis +package l1 package interpretation -package interprocedural import org.opalj.br.cfg.CFG 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.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP /** - * The `InterproceduralVirtualMethodCallInterpreter` is responsible for processing - * [[VirtualMethodCall]]s in an interprocedural fashion. + * 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) { +case class L1VirtualMethodCallInterpreter( + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler +) extends L1StringInterpreter { override type T = VirtualMethodCall[V] @@ -43,10 +38,6 @@ 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, defSite: Int): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { 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 0511d28226..54047ddf1b 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 @@ -8,7 +8,6 @@ package preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map - import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -117,20 +116,15 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * 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 - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] - * cannot infer / derive. For instance, if the exact value of an + * @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.IntraproceduralStringAnalysis]] + * [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]] * on another instance, store this information in fpe2Sci. - * @param resetExprHandler Whether to reset the underlying - * [[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.intraprocedural.IntraproceduralInterpretationHandler.reset]]. - * + * @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 * [[org.opalj.br.fpcf.properties.string_definition.StringTree]] will be returned. Note that * all elements of the tree will be defined, i.e., if `path` contains sites that could 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 9d281d23cb..d0b84e300a 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,7 +6,6 @@ package analyses import scala.collection.mutable import scala.collection.mutable.ListBuffer - import org.opalj.br.Method import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity @@ -18,15 +17,14 @@ import org.opalj.fpcf.EOptionP package object string_analysis { /** - * The type of entities the [[IntraproceduralStringAnalysis]] and the [[InterproceduralStringAnalysis]] process. + * The type of entities the string analysis process. * * @note The analysis require further context information, see [[SContext]]. */ type SEntity = PV /** - * [[IntraproceduralStringAnalysis]] and [[InterproceduralStringAnalysis]] process a local variable within a - * particular context, i.e. the method in which it is used. + * String analysis process a local variable within a particular context, i.e. the method in which it is used. */ type SContext = (SEntity, Method) From 4a9f2675343dae3b87aa5d63852aec75da5e4b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 6 Feb 2024 19:15:19 +0100 Subject: [PATCH 349/583] Fix formatting --- .../analyses/FieldAccessInformationKey.scala | 4 --- .../string_definition/StringTree.scala | 2 +- .../FloatValueInterpreter.scala | 4 +-- .../IntegerValueInterpreter.scala | 4 +-- .../InterpretationHandler.scala | 1 + .../interpretation/NewInterpreter.scala | 4 +-- .../interpretation/StringInterpreter.scala | 7 ++-- .../string_analysis/l0/L0StringAnalysis.scala | 10 +++--- .../interpretation/L0ArrayInterpreter.scala | 4 +-- .../L0InterpretationHandler.scala | 3 +- .../l1/L1ComputationState.scala | 8 ++--- .../string_analysis/l1/L1StringAnalysis.scala | 35 +++++++++---------- .../l1/finalizer/ArrayLoadFinalizer.scala | 4 +-- .../l1/finalizer/GetFieldFinalizer.scala | 2 +- .../l1/finalizer/NewArrayFinalizer.scala | 2 +- .../NonVirtualMethodCallFinalizer.scala | 2 +- .../StaticFunctionCallFinalizer.scala | 2 +- .../VirtualFunctionCallFinalizer.scala | 2 +- .../L1ArrayAccessInterpreter.scala | 14 ++++---- .../L1FieldReadInterpreter.scala | 20 +++++------ .../L1InterpretationHandler.scala | 30 ++++++++-------- .../L1NewArrayInterpreter.scala | 10 +++--- .../L1NonVirtualFunctionCallInterpreter.scala | 12 +++---- .../L1NonVirtualMethodCallInterpreter.scala | 8 ++--- .../L1StaticFunctionCallInterpreter.scala | 18 +++++----- .../interpretation/L1StringInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 14 ++++---- .../L1VirtualMethodCallInterpreter.scala | 4 +-- .../preprocessing/AbstractPathFinder.scala | 6 ++-- .../string_analysis/preprocessing/Path.scala | 10 +++--- .../preprocessing/PathTransformer.scala | 5 +-- .../string_analysis/string_analysis.scala | 1 + 32 files changed, 125 insertions(+), 129 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala index b1314562a5..5495fc3418 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/FieldAccessInformationKey.scala @@ -26,10 +26,6 @@ object FieldAccessInformationKey extends ProjectInformationKey[FieldAccessInform "analysis configuration", s"no field access information analysis configured, using SimpleFieldAccessInformationAnalysis as a fallback" )(project.logContext) - project.updateProjectInformationKeyInitializationData(this) { - case None => Seq(EagerSimpleFieldAccessInformationAnalysis) - case Some(schedulers) => schedulers ++ Seq(EagerSimpleFieldAccessInformationAnalysis) - } Seq(EagerSimpleFieldAccessInformationAnalysis) } 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 97790f7ca8..d0cd6cb639 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 @@ -359,7 +359,7 @@ case class StringTreeOr( * string may have (contain) a particular but not necessarily. */ case class StringTreeCond( - child: StringTree + child: StringTree ) extends StringTree(ListBuffer(child)) /** 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 index 6814054e30..73ff0e7777 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/FloatValueInterpreter.scala @@ -19,8 +19,8 @@ import org.opalj.fpcf.FinalEP * @author Maximilian Rüsch */ case class FloatValueInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler ) extends StringInterpreter { override type T = FloatConst 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 2d56a33ebc..829f799c34 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 @@ -20,8 +20,8 @@ import org.opalj.fpcf.FinalEP * @author Maximilian Rüsch */ case class IntegerValueInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler ) extends StringInterpreter { override type T = IntConst 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 7a4be2d66d..802fccdc73 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 @@ -8,6 +8,7 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.ObjectType import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty 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 ab6e0bb15b..15e031c0c0 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 @@ -16,8 +16,8 @@ import org.opalj.fpcf.FinalEP * @author Maximilian Rüsch */ case class NewInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: InterpretationHandler ) extends StringInterpreter { override type T = New diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 5427ee1c6c..679da7f927 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -8,6 +8,7 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.cfg.CFG @@ -81,9 +82,9 @@ trait StringInterpreter { */ protected def getMethodsForPC(pc: Int)( implicit - ps: PropertyStore, - callees: Callees, - contextProvider: ContextProvider + ps: PropertyStore, + callees: Callees, + contextProvider: ContextProvider ): (List[Method], Boolean) = { var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index ea65a4875a..570cb07722 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -6,6 +6,9 @@ package analyses package string_analysis package l0 +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis @@ -35,9 +38,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * 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''. @@ -253,9 +253,7 @@ class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } foundDependees case npe: NestedPathElement => - npe.element.foreach { nextSubpath => - foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) - } + npe.element.foreach { nextSubpath => foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) } foundDependees case _ => foundDependees } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala index 1ae094b059..246a5cc2f5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala @@ -18,8 +18,8 @@ import org.opalj.fpcf.FinalEP * @author Maximilian Rüsch */ case class L0ArrayInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L0InterpretationHandler ) extends L0StringInterpreter { override type T = ArrayLoad[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index deaa3be349..42ad3c712b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -79,7 +79,8 @@ class L0InterpretationHandler( FinalEP(expr, StringConstancyProperty.lb) case ExprStmt(_, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + case ExprStmt(_, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index f81e55eaf3..e7b4fd1397 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -6,21 +6,21 @@ package analyses package string_analysis package l1 +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.br.DeclaredMethod import org.opalj.br.Method import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.value.ValueInformation -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * 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 diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 164f59fe66..612f4edbbe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -6,6 +6,9 @@ package analyses package string_analysis package l1 +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.br.FieldType import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey @@ -46,9 +49,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * 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''. @@ -291,10 +291,9 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } var sci = StringConstancyInformation.lb - if ( - attemptFinalResultComputation - && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath, state) + if (attemptFinalResultComputation + && state.dependees.isEmpty + && computeResultsForPath(state.computedLeanPath, state) ) { // Check whether we deal with the empty string; it requires special treatment as the // PathTransformer#pathToStringTree would not handle it correctly (as @@ -437,9 +436,9 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } private def finalizePreparations( - path: Path, - state: L1ComputationState, - iHandler: L1InterpretationHandler + path: Path, + state: L1ComputationState, + iHandler: L1InterpretationHandler ): Unit = path.elements.foreach { case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { @@ -474,9 +473,9 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } private def processFinalP( - state: L1ComputationState, - e: Entity, - p: StringConstancyProperty + state: L1ComputationState, + e: Entity, + p: StringConstancyProperty ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { @@ -686,7 +685,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { * FlatPathElement.element in which it occurs. */ private def findDependeesAcc(subpath: SubPath, stmts: Array[Stmt[V]], target: SEntity)( - implicit tac: TACode[TACMethodParameter, V], + implicit tac: TACode[TACMethodParameter, V] ): ListBuffer[(SEntity, Int)] = { val dependees = ListBuffer[(SEntity, Int)]() subpath match { @@ -708,9 +707,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } dependees case npe: NestedPathElement => - npe.element.foreach { nextSubpath => - dependees.appendAll(findDependeesAcc(nextSubpath, stmts, target)) - } + npe.element.foreach { nextSubpath => dependees.appendAll(findDependeesAcc(nextSubpath, stmts, target)) } dependees case _ => dependees } @@ -727,7 +724,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { * this variable as `ignore`. */ private def findDependentVars(path: Path, stmts: Array[Stmt[V]], ignore: SEntity)( - implicit tac: TACode[TACMethodParameter, V], + implicit tac: TACode[TACMethodParameter, V] ): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() path.elements.foreach { nextSubpath => @@ -879,6 +876,6 @@ object LazyL1StringAnalysis override def requiredProjectInformation: ProjectInformationKeys = Seq( DeclaredMethodsKey, FieldAccessInformationKey, - ContextProviderKey, + ContextProviderKey ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala index 613bcb6f7a..013c8403b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala @@ -7,11 +7,11 @@ package string_analysis package l1 package finalizer +import scala.collection.mutable.ListBuffer + import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter -import scala.collection.mutable.ListBuffer - /** * @author Maximilian Rüsch */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala index 6021b24a6a..1c9f9cc7e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala @@ -11,7 +11,7 @@ package finalizer * @author Maximilian Rüsch */ case class GetFieldFinalizer( - override protected val state: L1ComputationState + override protected val state: L1ComputationState ) extends L1Finalizer { override protected type T = FieldRead[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala index 10bded6ea7..fc6378cedc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala @@ -11,7 +11,7 @@ package finalizer * @author Maximilian Rüsch */ case class NewArrayFinalizer( - override protected val state: L1ComputationState + override protected val state: L1ComputationState ) extends L1Finalizer { override type T = NewArray[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala index 778b88fafd..3412703224 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * @author Maximilian Rüsch */ case class NonVirtualMethodCallFinalizer( - override protected val state: L1ComputationState + override protected val state: L1ComputationState ) extends L1Finalizer { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala index 63f734ae69..2fb89d6c76 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * @author Maximilian Rüsch */ case class StaticFunctionCallFinalizer( - override protected val state: L1ComputationState + override protected val state: L1ComputationState ) extends L1Finalizer { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala index 0c531cbe8a..9a29ec61b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala @@ -15,7 +15,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType * @author Maximilian Rüsch */ case class VirtualFunctionCallFinalizer( - override protected val state: L1ComputationState + override protected val state: L1ComputationState ) extends L1Finalizer { override type T = VirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala index c911f0148e..255f0c987e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -7,12 +7,14 @@ package string_analysis package l1 package interpretation +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.collection.immutable.IntTrieSet -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore @@ -22,8 +24,6 @@ import org.opalj.tac.TACStmts import org.opalj.tac.V import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState -import scala.collection.mutable.ListBuffer - /** * Responsible for preparing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an interprocedural fashion. *

    @@ -35,10 +35,10 @@ import scala.collection.mutable.ListBuffer * @author Patrick Mell */ case class L1ArrayAccessInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, - params: List[Seq[StringConstancyInformation]] + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]] ) extends L1StringInterpreter { override type T = ArrayLoad[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 11813a8bbe..3b45eb66b3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -7,6 +7,8 @@ package string_analysis package l1 package interpretation +import scala.collection.mutable.ListBuffer + import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.cfg.CFG @@ -14,15 +16,13 @@ import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState - -import scala.collection.mutable.ListBuffer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis /** * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields @@ -31,12 +31,12 @@ import scala.collection.mutable.ListBuffer * @author Maximilian Rüsch */ case class L1FieldReadInterpreter( - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, - ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, - implicit val declaredFields: DeclaredFields, - implicit val contextProvider: ContextProvider + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + implicit val declaredFields: DeclaredFields, + implicit val contextProvider: ContextProvider ) extends L1StringInterpreter { override protected val cfg: CFG[Stmt[V], TACStmts[V]] = state.tac.cfg diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 917349b61f..552be62630 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -13,8 +13,8 @@ import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore @@ -26,11 +26,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.GetFieldFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NonVirtualMethodCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.GetFieldFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.StaticFunctionCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionCallFinalizer import org.opalj.value.ValueInformation /** @@ -43,12 +43,12 @@ import org.opalj.value.ValueInformation * @author Patrick Mell */ class L1InterpretationHandler( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - state: L1ComputationState, - contextProvider: ContextProvider + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + state: L1ComputationState, + contextProvider: ContextProvider ) extends InterpretationHandler(tac) { /** @@ -405,12 +405,12 @@ class L1InterpretationHandler( object L1InterpretationHandler { def apply( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - state: L1ComputationState, - contextProvider: ContextProvider + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + state: L1ComputationState, + contextProvider: ContextProvider ): L1InterpretationHandler = new L1InterpretationHandler( tac, ps, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index d8339ce2c6..561f23c9ee 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -10,8 +10,8 @@ package 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.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -25,10 +25,10 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ class L1NewArrayInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, - params: List[Seq[StringConstancyInformation]] + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]] ) extends L1StringInterpreter { override type T = NewArray[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 0dc3a39aad..1aee2fea87 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -10,9 +10,9 @@ package interpretation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore @@ -22,11 +22,11 @@ import org.opalj.fpcf.PropertyStore * @author Maximilian Rüsch */ case class L1NonVirtualFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - ps: PropertyStore, - state: L1ComputationState, - contextProvider: ContextProvider + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + contextProvider: ContextProvider ) extends L1StringInterpreter { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 88b2862537..b9992a98eb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -10,8 +10,8 @@ package 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.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP /** @@ -21,9 +21,9 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ case class L1NonVirtualMethodCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + state: L1ComputationState ) extends L1StringInterpreter { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index d26868cfa7..b78752247c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -7,20 +7,20 @@ package string_analysis package l1 package interpretation +import scala.util.Try + import org.opalj.br.ObjectType import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import scala.util.Try - /** * Responsible for processing [[StaticFunctionCall]]s in an interprocedural fashion. * For supported method calls, see the documentation of the `interpret` function. @@ -28,12 +28,12 @@ import scala.util.Try * @author Patrick Mell */ class L1StaticFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - ps: PropertyStore, - state: L1ComputationState, - params: List[Seq[StringConstancyInformation]], - contextProvider: ContextProvider + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]], + contextProvider: ContextProvider ) extends L1StringInterpreter { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 44e1e4449d..3c73cac7d9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -8,8 +8,8 @@ package l1 package interpretation import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 96bc9e87ea..491b916678 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -17,9 +17,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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore @@ -32,12 +32,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ class L1VirtualFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - ps: PropertyStore, - state: L1ComputationState, - params: List[Seq[StringConstancyInformation]], - contextProvider: ContextProvider + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler, + ps: PropertyStore, + state: L1ComputationState, + params: List[Seq[StringConstancyInformation]], + contextProvider: ContextProvider ) extends L1StringInterpreter { override type T = VirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 4fb214f0c4..97cd13e135 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -21,8 +21,8 @@ import org.opalj.fpcf.FinalEP * @author Patrick Mell */ case class L1VirtualMethodCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler + override protected val cfg: CFG[Stmt[V], TACStmts[V]], + override protected val exprHandler: L1InterpretationHandler ) extends L1StringInterpreter { override type T = VirtualMethodCall[V] 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 7197d47dbd..79fe7a98c6 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 @@ -507,10 +507,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * statements and that it determines itself whether the switch contains a default case or not. */ private def buildPathForSwitch( - start: Int, - end: Int, + start: Int, + end: Int, pathType: NestedPathType.Value, - fill: Boolean + fill: Boolean ): (Path, List[(Int, Int)]) = { val switch = cfg.code.instructions(start).asSwitch val caseStmts = ListBuffer[Int](switch.caseStmts.sorted: _*) 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 ac1681736a..48d9b79113 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 @@ -190,9 +190,9 @@ case class Path(elements: List[SubPath]) { * @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], - endSite: Int + toProcess: NestedPathElement, + siteMap: Map[Int, Unit], + endSite: Int ): Option[NestedPathElement] = { val elements = ListBuffer[SubPath]() @@ -205,8 +205,8 @@ case class Path(elements: List[SubPath]) { val leanedSubPath = makeLeanPathAcc(npe, siteMap, endSite) val keepAlternativeBranches = toProcess.elementType match { case Some(NestedPathType.CondWithAlternative) | - Some(NestedPathType.SwitchWithDefault) | - Some(NestedPathType.TryCatchFinally) => true + Some(NestedPathType.SwitchWithDefault) | + Some(NestedPathType.TryCatchFinally) => true case _ => false } if (leanedSubPath.isDefined) { 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 54047ddf1b..d2cda2c867 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 @@ -8,6 +8,7 @@ package preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map + import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -78,7 +79,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { Some(StringTreeCond(processedSubPaths.head)) } case NestedPathType.SwitchWithDefault | - NestedPathType.CondWithAlternative => + NestedPathType.CondWithAlternative => if (npe.element.size == processedSubPaths.size) { Some(StringTreeOr(processedSubPaths)) } else { @@ -88,7 +89,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { Some(StringTreeCond(StringTreeOr(processedSubPaths))) case NestedPathType.CondWithoutAlternative => Some(StringTreeCond(StringTreeOr(processedSubPaths))) - case _ => None + case _ => None } } else { None 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 d0b84e300a..1be9d747e9 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,6 +6,7 @@ package analyses import scala.collection.mutable import scala.collection.mutable.ListBuffer + import org.opalj.br.Method import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity From b7db6a1638ada11deebb074736f35c7743165c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 8 Feb 2024 15:25:22 +0100 Subject: [PATCH 350/583] Start to unify string analysis --- .../InterproceduralTestMethods.java | 12 + .../IntraProceduralTestMethods.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 29 +- OPAL/tac/src/main/resources/reference.conf | 6 +- .../string_analysis/ComputationState.scala | 258 +++++++++ .../string_analysis/StringAnalysis.scala | 491 ++++++++++++++++++ .../BinaryExprInterpreter.scala | 6 +- .../DoubleValueInterpreter.scala | 6 +- .../FloatValueInterpreter.scala | 6 +- .../IntegerValueInterpreter.scala | 6 +- .../InterpretationHandler.scala | 23 +- .../interpretation/NewInterpreter.scala | 6 +- .../StringConstInterpreter.scala | 6 +- .../interpretation/StringInterpreter.scala | 12 +- .../string_analysis/l0/L0StringAnalysis.scala | 210 ++------ .../interpretation/L0ArrayInterpreter.scala | 19 +- ...eter.scala => L0GetFieldInterpreter.scala} | 13 +- .../L0GetStaticInterpreter.scala | 11 +- .../L0InterpretationHandler.scala | 9 +- .../L0NonVirtualMethodCallInterpreter.scala | 17 +- .../L0StaticFunctionCallInterpreter.scala | 11 +- .../interpretation/L0StringInterpreter.scala | 20 +- .../L0VirtualFunctionCallInterpreter.scala | 150 +++--- .../L0VirtualMethodCallInterpreter.scala | 11 +- .../l1/L1ComputationState.scala | 241 +-------- .../string_analysis/l1/L1StringAnalysis.scala | 334 ++---------- .../l1/finalizer/GetFieldFinalizer.scala | 2 +- .../l1/finalizer/NewArrayFinalizer.scala | 2 +- .../L1ArrayAccessInterpreter.scala | 12 +- .../L1FieldReadInterpreter.scala | 48 +- .../L1InterpretationHandler.scala | 19 +- .../L1NewArrayInterpreter.scala | 11 +- .../L1NonVirtualFunctionCallInterpreter.scala | 10 +- .../L1NonVirtualMethodCallInterpreter.scala | 15 +- .../L1StaticFunctionCallInterpreter.scala | 15 +- .../interpretation/L1StringInterpreter.scala | 20 +- .../L1VirtualFunctionCallInterpreter.scala | 86 +-- .../L1VirtualMethodCallInterpreter.scala | 9 +- .../preprocessing/PathTransformer.scala | 6 +- 39 files changed, 1247 insertions(+), 923 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/{L0FieldReadInterpreter.scala => L0GetFieldInterpreter.scala} (56%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java index 9561a53aeb..be4a7235e7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java @@ -636,6 +636,18 @@ public void valueOfTest() { analyzeString(String.valueOf(getRuntimeClassName())); } + @StringDefinitionsCollection( + value = "a test case which tests the interpretation of String#valueOf", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.Runtime" + ) + }) + public void valueOfTest2() { + analyzeString(String.valueOf(getRuntimeClassName())); + } + @StringDefinitionsCollection( value = "a case where a static property is read", stringDefinitions = { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index e04596f090..d3c236e1d7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -746,7 +746,7 @@ public void withException(String filename) { expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ) }) - public void tryCatchFinally(String filename) { // TODO why three definitions here? + public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); try { String data = new String(Files.readAllBytes(Paths.get(filename))); 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 0b534ceecd..e9b56f6a8d 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 @@ -2,6 +2,9 @@ package org.opalj package fpcf +import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey + import java.net.URL import org.opalj.br.Annotation import org.opalj.br.Annotations @@ -113,14 +116,34 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/intraprocedural") + override def init(p: Project[URL]): Unit = { + val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(domain) + case Some(requirements) => requirements + domain + } + + p.get(RTACallGraphKey) + } + describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { val as = executeAnalyses(LazyL0StringAnalysis) val entities = determineEntitiesToAnalyze(as.project) - entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + val newEntities = entities + //.filter(entity => entity._2.name.startsWith("tryCatchFinally")) + //.filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + //.filterNot(entity => entity._2.name.startsWith("switchNested")) + //.filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + //.filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) + //.filterNot(entity => entity._2.name.startsWith("simpleStringConcat")) + //.filterNot(entity => entity._2.name.startsWith("multipleDefSites")) + //.filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) + newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.shutdown() - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + + validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) } } @@ -144,7 +167,7 @@ class InterproceduralStringAnalysisTest extends StringAnalysisTest { describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { val as = executeAnalyses(LazyL1StringAnalysis) - val entities = determineEntitiesToAnalyze(as.project) + val entities = determineEntitiesToAnalyze(as.project) //.filter(entity => entity._2.name == "valueOfTest2") entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.shutdown() diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index e15681ace4..3d887fbb74 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1493,7 +1493,11 @@ org.opalj { mergeExceptions = true }, cg.reflection.ReflectionRelatedCallsAnalysis.highSoundness = "" // e.g. "all" or "class,method", - fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false + fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, + string_analysis.l1.L1StringAnalysis = { + callersThreshold = 10, + fieldWriteThreshold = 100 + } } }, tac.cg { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala new file mode 100644 index 0000000000..6120738792 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -0,0 +1,258 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org +package opalj +package tac +package fpcf +package analyses +package string_analysis + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.br.DeclaredMethod +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.value.ValueInformation + +/** + * 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. + */ +trait ComputationState[State <: ComputationState[State]] { + val dm: DeclaredMethod + + /** + * The entity for which the analysis was started with. + */ + val entity: SContext + + /** + * The Three-Address Code of the entity's method + */ + var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ // TODO add dm to tac dependee mapping + + /** + * The interpretation handler to use for computing a final result (if possible). + */ + var iHandler: InterpretationHandler[State] = _ + + /** + * 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: InterpretationHandler[State] = _ + + /** + * The computed lean path that corresponds to the given entity + */ + var computedLeanPath: Path = _ + + /** + * 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[SEntity, ListBuffer[Int]] = mutable.Map() + + /** + * A mapping from values / indices of FlatPathElements to StringConstancyInformation + */ + val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + + /** + * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which are + * not yet final. + */ + 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[SEntity, 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[SContext, (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: 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[SContext, 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 [[StringConstancyInformation]] and extends the [[fpe2sci]] + * 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 + ): Unit = { + if (reset || !fpe2sci.contains(defSite)) { + fpe2sci(defSite) = ListBuffer() + } + if (!fpe2sci(defSite).contains(sci)) { + fpe2sci(defSite).append(sci) + } + } + + /** + * 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, + entity: Option[SEntity] = None + ): Unit = { + val numElements = var2IndexMapping.values.flatten.count(_ == defSite) + var addedNewList = false + if (!interimFpe2sci.contains(defSite)) { + interimFpe2sci(defSite) = ListBuffer() + addedNewList = true + } + // Append an element + 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) + } + } + } + + /** + * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. + */ + def appendToVar2IndexMapping(entity: SEntity, defSite: Int): Unit = { + if (!var2IndexMapping.contains(entity)) { + var2IndexMapping(entity) = ListBuffer() + } + 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/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala new file mode 100644 index 0000000000..0a50d24f23 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -0,0 +1,491 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.br.FieldType +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +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.fpcf.Entity +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimLUBP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter +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.NestedPathType +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.value.ValueInformation + +/** + * String Analysis trait defining some basic dependency handling. + * + * @author Patrick Mell + */ +trait StringAnalysis extends FPCFAnalysis { + + override val project: SomeProject + + type State <: ComputationState[State] + + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + + /** + * 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: State): InterimResult[StringConstancyProperty] = InterimResult( + state.entity, + computeNewLowerBound(state), + computeNewUpperBound(state), + state.dependees.toSet, + continuation(state) + ) + + private def computeNewUpperBound(state: State): StringConstancyProperty = { + if (state.computedLeanPath != null) { + StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( + state.computedLeanPath, + state.interimFpe2sci + )(state).reduce(true)) + } else { + StringConstancyProperty.lb + } + } + + private def computeNewLowerBound(state: State): StringConstancyProperty = StringConstancyProperty.lb + + /** + * 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. + */ + protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult + + /** + * 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. + * @return Returns a final result if (already) available. Otherwise, an intermediate result will + * be returned. + */ + protected[this] def continuation(state: State)(eps: SomeEPS): ProperPropertyComputationResult = { + state.dependees = state.dependees.filter(_.e != eps.e) + + eps match { + case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => + // 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) + case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + val e = entity.asInstanceOf[SContext] + // For updating the interim state + state.var2IndexMapping(eps.e.asInstanceOf[SContext]._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)) { + val pos = state.paramResultPositions(e) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(e) + state.parameterDependeesCount -= 1 + } + + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(e)) { + state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( + _, + p.stringConstancyInformation + )) + // Update the state + 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(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 + 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(_: StringConstancyProperty, ub: StringConstancyProperty) + if eps.pk.equals(StringConstancyProperty.key) => + state.dependees = eps :: state.dependees + val puVar = eps.e.asInstanceOf[SContext]._1 + state.var2IndexMapping(puVar).foreach { i => + state.appendToInterimFpe2Sci( + i, + ub.stringConstancyInformation, + Some(puVar) + ) + } + getInterimResult(state) + case _ => + state.dependees = eps :: state.dependees + getInterimResult(state) + + } + } + + protected[string_analysis] def finalizePreparations( + path: Path, + state: State, + iHandler: InterpretationHandler[State] + ): Unit = {} + + /** + * 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 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(state: State): Result = { + finalizePreparations(state.computedLeanPath, state, state.iHandler) + val finalSci = new PathTransformer(state.iHandler).pathToStringTree( + state.computedLeanPath, + state.fpe2sci, + resetExprHandler = false + )(state).reduce(true) + StringAnalysis.unregisterParams(state.entity) + Result(state.entity, StringConstancyProperty(finalSci)) + } + + protected def processFinalP( + state: State, + e: Entity, + p: StringConstancyProperty + ): ProperPropertyComputationResult = { + // Add mapping information (which will be used for computing the final result) + state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { + state.appendToFpe2Sci(_, p.stringConstancyInformation) + } + + state.dependees = state.dependees.filter(_.e != e) + // No more dependees => Return the result for this analysis run + if (state.dependees.isEmpty) { + computeFinalResult(state) + } else { + getInterimResult(state) + } + } + + /** + * 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 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. + */ + private def computeResultsForPath( + p: Path, + state: State + ): Boolean = { + var hasFinalResult = true + + p.elements.foreach { + case FlatPathElement(index) => + if (!state.fpe2sci.contains(index)) { + val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq))(state) + if (eOptP.isFinal) { + state.appendToFpe2Sci(index, eOptP.asFinal.p.stringConstancyInformation, reset = true) + } else { + hasFinalResult = false + } + } + case npe: NestedPathElement => + val subFinalResult = computeResultsForPath( + Path(npe.element.toList), + state + ) + hasFinalResult = hasFinalResult && subFinalResult + case _ => + } + + hasFinalResult + } + + /** + * This function is a wrapper function for [[computeLeanPathForStringConst]] and + * [[computeLeanPathForStringBuilder]]. + */ + protected def computeLeanPath( + value: V, + tac: TACode[TACMethodParameter, V] + ): Path = { + val defSites = value.definedBy.toArray.sorted + if (defSites.head < 0) { + computeLeanPathForStringConst(value) + } else { + val call = tac.stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val (leanPath, _) = computeLeanPathForStringBuilder(value, tac) + leanPath + } else { + computeLeanPathForStringConst(value) + } + } + } + + /** + * This function computes the lean path for a [[V]] which is required to be a string expression. + */ + protected def computeLeanPathForStringConst(value: V): Path = { + val defSites = value.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 [[V]] which is required to stem from a + * `String{Builder, Buffer}#toString()` call. For this, the `tac` of the method, in which `value` 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)`. + */ + protected def computeLeanPathForStringBuilder( + value: V, + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + ): (Path, Boolean) = { + val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) + val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) + if (initDefSites.isEmpty) { + (null, false) + } else { + val paths = pathFinder.findPaths(initDefSites, value.definedBy.toArray.max) + (paths.makeLeanPath(value, tac.stmts), true) + } + } + + private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { + def hasExprFormalParamUsage(expr: Expr[V]): Boolean = expr match { + case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) + case duVar: V => duVar.definedBy.exists(_ < 0) + case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) + case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) + case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) + case _ => false + } + + path.elements.exists { + case FlatPathElement(index) => stmts(index) match { + case Assignment(_, _, expr) => hasExprFormalParamUsage(expr) + case ExprStmt(_, expr) => hasExprFormalParamUsage(expr) + case _ => false + } + case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(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 + * [[FlatPathElement.element]] in which it occurs. + */ + private def findDependeesAcc(subpath: SubPath, stmts: Array[Stmt[V]])( + implicit state: State + ): ListBuffer[(SEntity, Int)] = { + val foundDependees = ListBuffer[(SEntity, 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.filter(_ >= 0).foreach { ds => + val expr = stmts(ds).asAssignment.expr + // TODO check support for passing nested string builder directly (e.g. with a test case) + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + foundDependees.append((param.toPersistentForm(state.tac.stmts), fpe.element)) + } + } + } + case _ => + } + foundDependees + case npe: NestedPathElement => + npe.element.foreach { nextSubpath => foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) } + foundDependees + case _ => foundDependees + } + } + + protected def findDependentVars(path: Path, stmts: Array[Stmt[V]], ignore: SEntity)( + implicit state: State + ): mutable.LinkedHashMap[SEntity, Int] = { + val dependees = mutable.LinkedHashMap[SEntity, Int]() + path.elements.foreach { nextSubpath => + findDependeesAcc(nextSubpath, stmts).foreach { nextPair => + if (ignore != nextPair._1) { + dependees.put(nextPair._1, nextPair._2) + } + } + } + dependees + } +} + +object StringAnalysis { + + /** + * 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[ListBuffer[StringConstancyInformation]]]() + + def registerParams(e: Entity, scis: ListBuffer[ListBuffer[StringConstancyInformation]]): Unit = { + if (!paramInfos.contains(e)) { + paramInfos(e) = scis + } else { + paramInfos(e).appendAll(scis) + } + } + + def unregisterParams(e: Entity): Unit = paramInfos.remove(e) + + def getParams(e: Entity): ListBuffer[ListBuffer[StringConstancyInformation]] = + if (paramInfos.contains(e)) { + paramInfos(e) + } else { + 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 = + v.value.isPrimitiveValue && isSupportedPrimitiveNumberType(v.value.asPrimitiveValue.primitiveType.toJava) + + /** + * 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 + * `typeName` may be approximated by the string analysis better than just the lower bound. + * + * @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" || isSupportedPrimitiveNumberType(typeName) || + typeName == "java.lang.String" || typeName == "java.lang.String[]" + + /** + * Determines whether a given element is supported by the string analysis. + * + * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, + * see [[StringAnalysis.isSupportedType(String)]]. + */ + def isSupportedType(v: V): Boolean = + if (v.value.isPrimitiveValue) { + isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) + } else { + try { + 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 [[StringAnalysis.isSupportedType(String)]]. + */ + 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) + } +} 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 b765adbd51..a2dcc70539 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 @@ -19,10 +19,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class BinaryExprInterpreter( +case class BinaryExprInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = BinaryExpr[V] 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 index 0e71142d25..f0c5fcbced 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/DoubleValueInterpreter.scala @@ -18,10 +18,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class DoubleValueInterpreter( +case class DoubleValueInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = DoubleConst 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 index 73ff0e7777..4807dee087 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/FloatValueInterpreter.scala @@ -18,10 +18,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class FloatValueInterpreter( +case class FloatValueInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = FloatConst 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 829f799c34..f75d1f000b 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 @@ -19,10 +19,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class IntegerValueInterpreter( +case class IntegerValueInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = IntConst 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 802fccdc73..dd740e30a5 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 @@ -20,7 +20,10 @@ import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis import org.opalj.value.ValueInformation -abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { +abstract class InterpretationHandler[State <: ComputationState[State]](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 @@ -55,7 +58,7 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): EOptionP[Entity, StringConstancyProperty] + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As @@ -68,13 +71,17 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value def reset(): Unit = { processedDefSites.clear() } + + /** + * Finalized a given definition state. + */ + def finalizeDefSite(defSite: Int, state: State): Unit } object InterpretationHandler { /** - * Checks whether an expression contains a call to [[StringBuilder#toString]] or - * [[StringBuffer#toString]]. + * Checks whether an expression contains a call to [[StringBuilder#toString]] or [[StringBuffer#toString]]. */ def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { @@ -255,15 +262,15 @@ object InterpretationHandler { ) /** - * @return Returns a [[StringConstancyProperty]] element that describes the result of a + * @return Returns a [[StringConstancyInformation]] 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 getStringConstancyPropertyForReplace: StringConstancyProperty = - StringConstancyProperty(StringConstancyInformation( + def getStringConstancyInformationForReplace: StringConstancyInformation = + StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.REPLACE, StringConstancyInformation.UnknownWordSymbol - )) + ) } 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 15e031c0c0..85956cfd52 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 @@ -15,10 +15,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class NewInterpreter( +case class NewInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = New 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 a01a880dd8..7e76647c26 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 @@ -22,10 +22,10 @@ import org.opalj.tac.V * * @author Maximilian Rüsch */ -case class StringConstInterpreter( +case class StringConstInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler -) extends StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends StringInterpreter[State] { override type T = StringConst diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 679da7f927..0ee5908c87 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -21,15 +21,13 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI import org.opalj.value.ValueInformation /** * @author Maximilian Rüsch */ -trait StringInterpreter { +trait StringInterpreter[State <: ComputationState[State]] { /** * The control flow graph that underlies the instruction to interpret. @@ -44,7 +42,7 @@ trait StringInterpreter { * clearly indicate what kind of [[InterpretationHandler]] they expect in order to ensure the * desired behavior and not confuse developers. */ - protected val exprHandler: InterpretationHandler + protected val exprHandler: InterpretationHandler[State] type T <: Any @@ -62,7 +60,7 @@ trait StringInterpreter { protected def getTACAI( ps: PropertyStore, m: Method, - s: L1ComputationState + s: State ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { @@ -134,11 +132,11 @@ trait StringInterpreter { */ protected def evaluateParameters( params: List[Seq[Expr[V]]], - iHandler: L1InterpretationHandler, + iHandler: InterpretationHandler[State], funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] - ): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { + )(implicit state: State): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { case (nextParam, middleIndex) => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 570cb07722..775650bcaa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -6,9 +6,7 @@ package analyses package string_analysis package l0 -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - +import org.opalj.br.DeclaredMethod import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis @@ -17,7 +15,6 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult @@ -29,14 +26,19 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler -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.value.ValueInformation + +/** + * 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. + */ +protected[l0] case class L0ComputationState( + override val dm: DeclaredMethod, + override val entity: SContext +) extends ComputationState[L0ComputationState] /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program @@ -62,28 +64,11 @@ import org.opalj.value.ValueInformation * * @author Patrick Mell */ -class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { +class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - /** - * 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[SEntity, 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]] - ) + override type State = L0ComputationState def analyze(data: SContext): ProperPropertyComputationResult = { - // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyInformation.lb - // Retrieve TAC from property store val tacOpt: Option[TACode[TACMethodParameter, V]] = ps(data._2, TACAI.key) match { case UBP(tac) => if (tac.tac.isEmpty) None else Some(tac.tac.get) @@ -93,31 +78,38 @@ class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { if (tacOpt.isEmpty) return Result(data, StringConstancyProperty.lb) // TODO add continuation - implicit val tac: TACode[TACMethodParameter, V] = tacOpt.get + val tac = tacOpt.get + val state = L0ComputationState(declaredMethods(data._2), data) + state.iHandler = L0InterpretationHandler(tac) + state.interimIHandler = L0InterpretationHandler(tac) + state.tac = tac + determinePossibleStrings(state) + } + + override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { + implicit val _state: State = state + + // sci stores the final StringConstancyInformation (if it can be determined now at all) + var sci = StringConstancyInformation.lb + + implicit val tac: TACode[TACMethodParameter, V] = state.tac val stmts = tac.stmts - val puVar = data._1 + val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(tac.pcToIndex) 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 handled further down if (defSites.head < 0) { - return Result(data, StringConstancyProperty.lb) + return Result(state.entity, StringConstancyProperty.lb) } - // If not empty, this very routine can only produce an intermediate result - val dependees: mutable.Map[SContext, ListBuffer[EOptionP[SContext, StringConstancyProperty]]] = 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 - var state: ComputationState = null - val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { val initDefSites = InterpretationHandler.findDefSiteOfInit(uVar, stmts) if (initDefSites.isEmpty) { // String{Builder,Buffer} from method parameter is to be evaluated - return Result(data, StringConstancyProperty.lb) + return Result(state.entity, StringConstancyProperty.lb) } val path = new WindowPathFinder(tac.cfg).findPaths(initDefSites, uVar.definedBy.head) @@ -126,17 +118,15 @@ class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(leanPath, stmts, puVar) if (dependentVars.nonEmpty) { - state = ComputationState(leanPath, dependentVars, mutable.Map[Int, StringConstancyInformation](), tac) + state.computedLeanPath = leanPath + dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } + dependentVars.keys.foreach { nextVar => - propertyStore((nextVar, data._2), StringConstancyProperty.key) match { + propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { case finalEP: FinalEP[SContext, StringConstancyProperty] => - if (dependees.contains(data)) - dependees(data) = dependees(data).filter { _.e != finalEP.e } - val sciOpt = processFinalP(dependees.values.flatten, state, finalEP) - if (sciOpt.isDefined) - sci = sciOpt.get + return processFinalP(state, finalEP.e, finalEP.p) case ep => - dependees.getOrElseUpdate(data, ListBuffer()).append(ep) + state.dependees = ep :: state.dependees } } } else { @@ -148,142 +138,42 @@ class L0StringAnalysis(val project: SomeProject) extends FPCFAnalysis { val interpretationHandler = L0InterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => - interpretationHandler.processDefSite(ds).p.stringConstancyInformation + interpretationHandler.processDefSite(ds).asFinal.p.stringConstancyInformation } ) } - if (dependees.nonEmpty) { + if (state.dependees.nonEmpty) { InterimResult( - data._1, + state.entity._1, StringConstancyProperty.ub, StringConstancyProperty.lb, - dependees.values.flatten.toSet, - continuation(data, dependees.values.flatten, state) + state.dependees.toSet, + continuation(state) ) } else { - Result(data, StringConstancyProperty(sci)) - } - } - - private def processFinalP( - dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], - state: ComputationState, - finalEP: FinalEP[SContext, StringConstancyProperty] - ): Option[StringConstancyInformation] = { - // Add mapping information (which will be used for computing the final result) - state.fpe2sci.put(state.var2IndexMapping(finalEP.e._1), finalEP.p.stringConstancyInformation) - - if (dependees.isEmpty) { - val sci = new PathTransformer(L0InterpretationHandler(state.tac)).pathToStringTree( - state.computedLeanPath, - state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } - ).reduce(true) - Some(sci) - } else { - None + Result(state.entity, StringConstancyProperty(sci)) } } /** * 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: SContext, - dependees: Iterable[EOptionP[SContext, StringConstancyProperty]], - state: ComputationState + override protected def continuation( + state: State )(eps: SomeEPS): ProperPropertyComputationResult = eps match { case finalEP: FinalEP[_, _] => val finalScpEP = finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]] - val sciOpt = processFinalP(dependees, state, finalScpEP) - if (sciOpt.isDefined) { - val finalSci = new PathTransformer(L0InterpretationHandler(state.tac)).pathToStringTree( - state.computedLeanPath, - state.fpe2sci.map { case (k, v) => (k, ListBuffer(v)) } - ).reduce(true) - Result(data, StringConstancyProperty(finalSci)) - } else { - val remainingDependees = dependees.filter { _.e != finalScpEP.e } - InterimResult( - data, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - remainingDependees.toSet, - continuation(data, remainingDependees, state) - ) - } - - case InterimLUBP(lb, ub) => InterimResult(data, lb, ub, dependees.toSet, 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]] - )(implicit tac: TACode[TACMethodParameter, V]): ListBuffer[(SEntity, Int)] = { - val foundDependees = ListBuffer[(SEntity, 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.filter(_ >= 0).foreach { ds => - val expr = stmts(ds).asAssignment.expr - // TODO check support for passing nested string builder directly (e.g. with a test case) - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append((param.toPersistentForm(tac.stmts), fpe.element)) - } - } - } - case _ => - } - foundDependees - case npe: NestedPathElement => - npe.element.foreach { nextSubpath => foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) } - 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 - * this variable as `ignore`. - */ - private def findDependentVars( - path: Path, - stmts: Array[Stmt[V]], - ignore: SEntity - )(implicit tac: TACode[TACMethodParameter, V]): mutable.LinkedHashMap[SEntity, Int] = { - val dependees = mutable.LinkedHashMap[SEntity, Int]() + processFinalP(state, finalScpEP.e, finalScpEP.p) - path.elements.foreach { nextSubpath => - findDependeesAcc(nextSubpath, stmts).foreach { nextPair => - if (ignore != nextPair._1) { - dependees.put(nextPair._1, nextPair._2) - } - } - } - dependees + case InterimLUBP(lb, ub) => + InterimResult(state.entity, lb, ub, state.dependees.toSet, continuation(state)) + case _ => + throw new IllegalStateException("Could not process the continuation successfully.") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala index 246a5cc2f5..e6638138ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala @@ -10,21 +10,24 @@ package 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.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an intraprocedural fashion. * * @author Maximilian Rüsch */ -case class L0ArrayInterpreter( +case class L0ArrayInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = ArrayLoad[V] - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val stmts = cfg.code.instructions val defSites = instr.arrayRef.asVar.definedBy.toArray var scis = Seq.empty[StringConstancyInformation] @@ -34,14 +37,10 @@ case class L0ArrayInterpreter( stmts(_) match { // Process ArrayStores case ArrayStore(_, _, _, value) => - scis = scis ++ value.asVar.definedBy.toArray.sorted.map { - exprHandler.processDefSite(_).p.stringConstancyInformation - } + scis = scis ++ value.asVar.definedBy.toArray.sorted.flatMap { handleDependentDefSite } // Process ArrayLoads case Assignment(_, _, expr: ArrayLoad[V]) => - scis = scis ++ expr.arrayRef.asVar.definedBy.toArray.sorted.map { - exprHandler.processDefSite(_).p.stringConstancyInformation - } + scis = scis ++ expr.arrayRef.asVar.definedBy.toArray.sorted.flatMap { handleDependentDefSite } case _ => } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala similarity index 56% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala index 34197a5ded..8d4ed29a48 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala @@ -9,24 +9,27 @@ package interpretation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[FieldRead]]s. Currently, there is no support for fields, i.e., they are not analyzed but + * Responsible for processing [[GetField]]s. Currently, there is no support for fields, i.e., they are not analyzed but * a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned. * * @author Maximilian Rüsch */ -case class L0FieldReadInterpreter( +case class L0GetFieldInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = GetField[V] /** * Fields are currently unsupported, thus this function always returns [[StringConstancyProperty.lb]]. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala index 8775bf7500..0fb76c25d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala @@ -9,7 +9,10 @@ package interpretation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `IntraproceduralGetStaticInterpreter` is responsible for processing @@ -19,16 +22,16 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -case class L0GetStaticInterpreter( +case class L0GetStaticInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = GetStatic /** * Currently, this type is not interpreted. Thus, this function always returns [[StringConstancyProperty.lb]]. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 42ad3c712b..60b75ccfff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter @@ -31,7 +32,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt */ class L0InterpretationHandler( tac: TACode[TACMethodParameter, V] -) extends InterpretationHandler(tac) { +) extends InterpretationHandler[L0ComputationState](tac) { /** * Processed the given definition site in an intraprocedural fashion. @@ -41,7 +42,7 @@ class L0InterpretationHandler( override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): FinalEP[Entity, StringConstancyProperty] = { + )(implicit state: L0ComputationState): 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 @@ -69,7 +70,7 @@ class L0InterpretationHandler( case Assignment(_, _, expr: New) => NewInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: GetField[V]) => - L0FieldReadInterpreter(cfg, this).interpret(expr, defSite) + L0GetFieldInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => @@ -88,6 +89,8 @@ class L0InterpretationHandler( case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) } } + + override def finalizeDefSite(defSite: Int, state: L0ComputationState): Unit = {} } object L0InterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index dc201763d6..c9ca685b8b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -10,7 +10,10 @@ package 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.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[NonVirtualMethodCall]]s in an intraprocedural fashion. @@ -18,10 +21,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter( +case class L0NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = NonVirtualMethodCall[V] @@ -37,7 +40,7 @@ case class L0NonVirtualMethodCallInterpreter( * * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { case "" => interpretInit(instr) case _ => StringConstancyProperty.getNeutralElement @@ -52,13 +55,11 @@ case class L0NonVirtualMethodCallInterpreter( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit(init: T): StringConstancyProperty = { + private def interpretInit(init: T)(implicit state: State): StringConstancyProperty = { init.params.size match { case 0 => StringConstancyProperty.getNeutralElement case _ => - val scis = init.params.head.asVar.definedBy.toList.map { ds => - exprHandler.processDefSite(ds).p.stringConstancyInformation - } + val scis = init.params.head.asVar.definedBy.toList.flatMap { handleDependentDefSite } StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 53c96faee2..fd83497ecd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -9,7 +9,10 @@ package interpretation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `IntraproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -19,13 +22,13 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter( +case class L0StaticFunctionCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index 66808a0965..f1cca79d6a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -8,16 +8,16 @@ package l0 package interpretation import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity -import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** * @author Maximilian Rüsch */ -trait L0StringInterpreter extends StringInterpreter { - - override protected val exprHandler: L0InterpretationHandler +trait L0StringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { /** * @param instr The instruction that is to be interpreted. It is the responsibility of implementations to make sure @@ -35,5 +35,15 @@ trait L0StringInterpreter extends StringInterpreter { * 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, defSite: Int): FinalEP[Entity, StringConstancyProperty] + def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] + + protected def handleDependentDefSite(defSite: Int)(implicit state: State): Option[StringConstancyInformation] = { + exprHandler.processDefSite(defSite) match { + case FinalP(p) => + Some(p.stringConstancyInformation) + case eps => + state.dependees = eps :: state.dependees + None + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 955041a8d9..3c5ea80dc5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -18,6 +18,8 @@ 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.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler @@ -27,10 +29,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -case class L0VirtualFunctionCallInterpreter( +case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = VirtualFunctionCall[V] @@ -57,25 +59,24 @@ case class L0VirtualFunctionCallInterpreter( * If none of the above-described cases match, a result containing * [[StringConstancyProperty.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { - val property = instr.name match { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + val sci = instr.name match { case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) - case "replace" => interpretReplaceCall(instr) + case "replace" => Some(interpretReplaceCall) case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj.fqn == "java/lang/String" => StringConstancyProperty.lb - case FloatType | DoubleType => - StringConstancyProperty(StringConstancyInformation( + case obj: ObjectType if obj.fqn == "java/lang/String" => Some(StringConstancyInformation.lb) + case FloatType | DoubleType => Some(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.FloatValue )) - case _ => StringConstancyProperty.getNeutralElement + case _ => Some(StringConstancyInformation.getNeutralElement) } } - FinalEP(instr, property) + FinalEP(instr, StringConstancyProperty(sci.get)) } /** @@ -83,38 +84,46 @@ case class L0VirtualFunctionCallInterpreter( * 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]): StringConstancyProperty = { + private def interpretAppendCall(appendCall: VirtualFunctionCall[V])(implicit + state: State + ): Option[StringConstancyInformation] = { val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation - val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation + val appendSci = valueOfAppendCall(appendCall) - val sci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { - // although counter-intuitive, this case may occur if both the receiver and the parameter have been - // processed before - StringConstancyInformation.getNeutralElement - } else if (receiverSci.isTheNeutralElement) { - // 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 - appendSci - } else if (appendSci.isTheNeutralElement) { - // The append value might be empty, if the site has already been processed (then this - // information will come from another StringConstancyInformation object - receiverSci + if (appendSci.isEmpty) { + None } else { - // Receiver and parameter information are available => combine them - StringConstancyInformation( - StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.constancyLevel), - StringConstancyType.APPEND, - receiverSci.possibleStrings + appendSci.possibleStrings - ) - } + val sci = if (receiverSci.isTheNeutralElement && appendSci.get.isTheNeutralElement) { + // although counter-intuitive, this case may occur if both the receiver and the parameter have been + // processed before + StringConstancyInformation.getNeutralElement + } else if (receiverSci.isTheNeutralElement) { + // 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 + appendSci.get + } else if (appendSci.get.isTheNeutralElement) { + // The append value might be empty, if the site has already been processed (then this + // information will come from another StringConstancyInformation object + receiverSci + } else { + // Receiver and parameter information are available => combine them + StringConstancyInformation( + StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.get.constancyLevel), + StringConstancyType.APPEND, + receiverSci.possibleStrings + appendSci.get.possibleStrings + ) + } - StringConstancyProperty(sci) + Some(sci) + } } /** * This function determines the current value of the receiver object of an `append` call. */ - private def receiverValuesOfAppendCall(call: VirtualFunctionCall[V]): StringConstancyProperty = { + private def receiverValuesOfAppendCall(call: VirtualFunctionCall[V])(implicit + state: State + ): 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 { ds => @@ -128,45 +137,48 @@ case class L0VirtualFunctionCallInterpreter( * 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]): StringConstancyProperty = { + private def valueOfAppendCall(call: VirtualFunctionCall[V])(implicit + state: State + ): Option[StringConstancyInformation] = { 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).p + var value = handleDependentDefSite(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) { - val r = exprHandler.processDefSite( - cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min - ) - value = r.p + if (value.isDefined && value.get.isTheNeutralElement) { + value = handleDependentDefSite(cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min) } - 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 && - sci.constancyLevel == StringConstancyLevel.CONSTANT - ) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) - } else { - sci - } - case ComputationalTypeFloat | ComputationalTypeDouble => - if (sci.constancyLevel == StringConstancyLevel.CONSTANT) { + if (value.isEmpty) { + None + } else { + val sci = value.get + 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) + } else { + sci + } + case ComputationalTypeFloat | ComputationalTypeDouble => + if (sci.constancyLevel == StringConstancyLevel.CONSTANT) { + sci + } else { + InterpretationHandler.getConstancyInfoForDynamicFloat + } + // Otherwise, try to compute + case _ => sci - } else { - InterpretationHandler.getConstancyInfoForDynamicFloat - } - // Otherwise, try to compute - case _ => - sci - } + } - StringConstancyProperty(finalSci) + Some(finalSci) + } } /** @@ -174,8 +186,10 @@ case class L0VirtualFunctionCallInterpreter( * 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]): StringConstancyProperty = { - exprHandler.processDefSite(call.receiver.asVar.definedBy.head).p + private def interpretToStringCall(call: VirtualFunctionCall[V])(implicit + state: State + ): Option[StringConstancyInformation] = { + handleDependentDefSite(call.receiver.asVar.definedBy.head) } /** @@ -183,6 +197,6 @@ case class L0VirtualFunctionCallInterpreter( * (Currently, this function simply approximates `replace` functions by returning the lower * bound of [[StringConstancyProperty]]). */ - private def interpretReplaceCall(instr: VirtualFunctionCall[V]): StringConstancyProperty = - InterpretationHandler.getStringConstancyPropertyForReplace + private def interpretReplaceCall: StringConstancyInformation = + InterpretationHandler.getStringConstancyInformationForReplace } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 7a209163ff..d7e54efdd2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -12,7 +12,10 @@ 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.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[VirtualMethodCall]]s in an intraprocedural fashion. @@ -20,10 +23,10 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter( +case class L0VirtualMethodCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L0InterpretationHandler -) extends L0StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] { override type T = VirtualMethodCall[V] @@ -40,7 +43,7 @@ case class L0VirtualMethodCallInterpreter( * * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) case _ => StringConstancyInformation.getNeutralElement diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index e7b4fd1397..e7f25be135 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -6,20 +6,9 @@ package analyses package string_analysis package l1 -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.br.DeclaredMethod -import org.opalj.br.Method import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.Property -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path -import org.opalj.value.ValueInformation /** * This class is to be used to store state information that are required at a later point in @@ -27,31 +16,11 @@ import org.opalj.value.ValueInformation * 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 - * [[L1StringAnalysis#fieldWriteThreshold]]. */ -case class L1ComputationState(dm: DeclaredMethod, entity: SContext, fieldWriteThreshold: Int = 100) { - /** - * The Three-Address Code of the entity's method - */ - var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ - - /** - * The interpretation handler to use for computing a final result (if possible). - */ - var iHandler: L1InterpretationHandler = _ - - /** - * 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: L1InterpretationHandler = _ - - /** - * The computed lean path that corresponds to the given entity - */ - var computedLeanPath: Path = _ +case class L1ComputationState( + override val dm: DeclaredMethod, + override val entity: SContext +) extends ComputationState[L1ComputationState] { /** * Callees information regarding the declared method that corresponds to the entity's method @@ -62,206 +31,4 @@ case class L1ComputationState(dm: DeclaredMethod, entity: SContext, fieldWriteTh * Callers information regarding the declared method that corresponds to the entity's method */ var callers: Callers = _ - - /** - * 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[SEntity, ListBuffer[Int]] = mutable.Map() - - /** - * A mapping from values / indices of FlatPathElements to StringConstancyInformation - */ - val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() - - /** - * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which are - * not yet final. - */ - 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[SEntity, 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[SContext, (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: 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[SContext, 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 [[StringConstancyInformation]] and extends the [[fpe2sci]] - * 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 - ): Unit = { - if (reset || !fpe2sci.contains(defSite)) { - fpe2sci(defSite) = ListBuffer() - } - if (!fpe2sci(defSite).contains(sci)) { - fpe2sci(defSite).append(sci) - } - } - - /** - * 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, - entity: Option[SEntity] = None - ): Unit = { - val numElements = var2IndexMapping.values.flatten.count(_ == defSite) - var addedNewList = false - if (!interimFpe2sci.contains(defSite)) { - interimFpe2sci(defSite) = ListBuffer() - addedNewList = true - } - // Append an element - 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) - } - } - } - - /** - * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. - */ - def appendToVar2IndexMapping(entity: SEntity, defSite: Int): Unit = { - if (!var2IndexMapping.contains(entity)) { - var2IndexMapping(entity) = ListBuffer() - } - 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/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 612f4edbbe..23d32256a0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -10,8 +10,10 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.FieldType +import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject @@ -28,26 +30,23 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel 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 import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -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.NestedPathType 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.value.ValueInformation /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -74,39 +73,46 @@ import org.opalj.value.ValueInformation * * @author Patrick Mell */ -class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { +class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - private val contextProvider: ContextProvider = project.get(ContextProviderKey) + override type State = L1ComputationState - // 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 + private val callersThreshold = { + val threshold = + try { + project.config.getInt(L1StringAnalysis.CallersThresholdConfigKey) + } catch { + case t: Throwable => + logOnce(Error( + "analysis configuration - l1 string analysis", + s"couldn't read: ${L1StringAnalysis.CallersThresholdConfigKey}", + t + )) + 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 val declaredFields = project.get(DeclaredFieldsKey) - private val fieldAccessInformation = project.get(FieldAccessInformationKey) + logOnce(Info( + "analysis configuration - l1 string analysis", + "l1 string analysis uses a callers threshold of " + threshold + )) + threshold + } + + protected implicit val declaredFields: DeclaredFields = project.get(DeclaredFieldsKey) + protected implicit val fieldAccessInformation: FieldAccessInformation = project.get(FieldAccessInformationKey) + protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) /** * 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: L1ComputationState - ): InterimResult[StringConstancyProperty] = InterimResult( + private def getInterimResult(state: State): InterimResult[StringConstancyProperty] = InterimResult( state.entity, computeNewLowerBound(state), computeNewUpperBound(state), @@ -114,26 +120,22 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { continuation(state) ) - private def computeNewUpperBound( - state: L1ComputationState - ): StringConstancyProperty = { + private def computeNewUpperBound(state: State): StringConstancyProperty = { if (state.computedLeanPath != null) { StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( state.computedLeanPath, state.interimFpe2sci - ).reduce(true)) + )(state).reduce(true)) } else { StringConstancyProperty.lb } } - private def computeNewLowerBound( - state: L1ComputationState - ): StringConstancyProperty = StringConstancyProperty.lb + private def computeNewLowerBound(state: State): StringConstancyProperty = StringConstancyProperty.lb def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) - val state = l1.L1ComputationState(dm, data, fieldWriteThreshold) + val state = L1ComputationState(dm, data) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { @@ -162,7 +164,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { * 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(state: L1ComputationState): ProperPropertyComputationResult = { + override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted @@ -181,6 +183,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { state.iHandler = L1InterpretationHandler( state.tac, ps, + project, declaredFields, fieldAccessInformation, state, @@ -195,6 +198,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { state.interimIHandler = L1InterpretationHandler( state.tac, ps, + project, declaredFields, fieldAccessInformation, interimState, @@ -264,7 +268,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { // 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.map(_.toList)) + val r = state.iHandler.processDefSite(defSites.head, state.params.toList.map(_.toList))(state) return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) } @@ -272,7 +276,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { var attemptFinalResultComputation = false if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(state.computedLeanPath, stmts, puVar)(state.tac) + val dependentVars = findDependentVars(state.computedLeanPath, stmts, puVar)(state) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar => dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } @@ -313,7 +317,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci - ).reduce(true) + )(state).reduce(true) } } @@ -334,19 +338,12 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { * @return Returns a final result if (already) available. Otherwise, an intermediate result will * be returned. */ - private def continuation( + override protected def continuation( state: L1ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != eps.e) eps match { - case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => - // 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) case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => state.callees = callees if (state.dependees.isEmpty) { @@ -362,83 +359,15 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { } else { getInterimResult(state) } - case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - val e = entity.asInstanceOf[SContext] - // For updating the interim state - state.var2IndexMapping(eps.e.asInstanceOf[SContext]._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)) { - val pos = state.paramResultPositions(e) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(e) - state.parameterDependeesCount -= 1 - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(e)) { - state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( - _, - p.stringConstancyInformation - )) - // Update the state - 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(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 - 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(_: StringConstancyProperty, ub: StringConstancyProperty) - if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = eps :: state.dependees - val puVar = eps.e.asInstanceOf[SContext]._1 - state.var2IndexMapping(puVar).foreach { i => - state.appendToInterimFpe2Sci( - i, - ub.stringConstancyInformation, - Some(puVar) - ) - } - getInterimResult(state) case _ => - state.dependees = eps :: state.dependees - getInterimResult(state) - + super.continuation(state)(eps) } } - private def finalizePreparations( + override protected[string_analysis] def finalizePreparations( path: Path, - state: L1ComputationState, - iHandler: L1InterpretationHandler + state: State, + iHandler: InterpretationHandler[State] ): Unit = path.elements.foreach { case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { @@ -449,48 +378,6 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { 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 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(state: L1ComputationState): Result = { - finalizePreparations(state.computedLeanPath, state, state.iHandler) - val finalSci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, - state.fpe2sci, - resetExprHandler = false - ).reduce(true) - L1StringAnalysis.unregisterParams(state.entity) - Result(state.entity, StringConstancyProperty(finalSci)) - } - - private def processFinalP( - state: L1ComputationState, - e: Entity, - p: StringConstancyProperty - ): ProperPropertyComputationResult = { - // Add mapping information (which will be used for computing the final result) - state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { - state.appendToFpe2Sci(_, p.stringConstancyInformation) - } - - state.dependees = state.dependees.filter(_.e != e) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } - } - /** * 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 @@ -576,7 +463,7 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { p.elements.foreach { case FlatPathElement(index) => if (!state.fpe2sci.contains(index)) { - val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq)) + val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq))(state) if (eOptP.isFinal) { state.appendToFpe2Sci(index, eOptP.asFinal.p.stringConstancyInformation, reset = true) } else { @@ -595,69 +482,6 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { hasFinalResult } - /** - * This function is a wrapper function for [[computeLeanPathForStringConst]] and - * [[computeLeanPathForStringBuilder]]. - */ - private def computeLeanPath( - value: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): Path = { - val defSites = value.definedBy.toArray.sorted - if (defSites.head < 0) { - computeLeanPathForStringConst(value) - } else { - val call = tac.stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (leanPath, _) = computeLeanPathForStringBuilder(value, tac) - leanPath - } else { - computeLeanPathForStringConst(value) - } - } - } - - /** - * This function computes the lean path for a [[DUVar]] which is required to be a string - * expressions. - */ - private def computeLeanPathForStringConst(value: V): Path = { - val defSites = value.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( - value: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): (Path, Boolean) = { - val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) - val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) - if (initDefSites.isEmpty) { - (null, false) - } else { - val paths = pathFinder.findPaths(initDefSites, value.definedBy.toArray.max) - (paths.makeLeanPath(value, tac.stmts), true) - } - } - private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { def hasExprFormalParamUsage(expr: Expr[V]): Boolean = expr match { case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) @@ -678,67 +502,17 @@ class L1StringAnalysis(val project: SomeProject) extends FPCFAnalysis { 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 - * FlatPathElement.element in which it occurs. - */ - private def findDependeesAcc(subpath: SubPath, stmts: Array[Stmt[V]], target: SEntity)( - implicit tac: TACode[TACMethodParameter, V] - ): ListBuffer[(SEntity, Int)] = { - val dependees = ListBuffer[(SEntity, 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.filter(_ >= 0).foreach { ds => - val expr = stmts(ds).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - dependees.append((param.toPersistentForm(tac.stmts), fpe.element)) - } - } - } - case _ => - } - dependees - case npe: NestedPathElement => - npe.element.foreach { nextSubpath => dependees.appendAll(findDependeesAcc(nextSubpath, stmts, target)) } - dependees - case _ => dependees - } - } +object L1StringAnalysis { - /** - * 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: SEntity)( - implicit tac: TACode[TACMethodParameter, V] - ): mutable.LinkedHashMap[SEntity, Int] = { - val dependees = mutable.LinkedHashMap[SEntity, Int]() - path.elements.foreach { nextSubpath => - findDependeesAcc(nextSubpath, stmts, ignore).foreach { nextPair => - if (ignore != nextPair._1) { - dependees.put(nextPair._1, nextPair._2) - } - } - } - dependees + final val FieldWriteThresholdConfigKey = { + "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.fieldWriteThreshold" } -} -object L1StringAnalysis { + private final val CallersThresholdConfigKey = { + "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.callersThreshold" + } /** * Maps entities to a list of lists of parameters. As currently this analysis works context- diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala index 1c9f9cc7e2..ba58c58d72 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala @@ -24,5 +24,5 @@ case class GetFieldFinalizer( 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. - state.iHandler.processDefSite(defSite) + state.iHandler.processDefSite(defSite)(state) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala index fc6378cedc..57f5299c7f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala @@ -23,5 +23,5 @@ case class NewArrayFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = // Simply re-trigger the computation - state.iHandler.processDefSite(defSite) + state.iHandler.processDefSite(defSite)(state) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala index 255f0c987e..65d7ee5b76 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -22,7 +22,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.V -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for preparing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an interprocedural fashion. @@ -34,12 +34,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState * * @author Patrick Mell */ -case class L1ArrayAccessInterpreter( +case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, + override protected val exprHandler: InterpretationHandler[State], + state: State, params: List[Seq[StringConstancyInformation]] -) extends L1StringInterpreter { +) extends L1StringInterpreter[State] { override type T = ArrayLoad[V] @@ -49,7 +49,7 @@ case class L1ArrayAccessInterpreter( * definition sites producing a refinable result will have to be handled later on to * not miss this information. */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 3b45eb66b3..6edc0888a9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -21,7 +22,10 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1ComputationState +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.OPALLogger.logOnce +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis /** @@ -30,19 +34,49 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * * @author Maximilian Rüsch */ -case class L1FieldReadInterpreter( - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, +case class L1FieldReadInterpreter[State <: ComputationState[State]]( + override protected val exprHandler: InterpretationHandler[State], + state: State, ps: PropertyStore, fieldAccessInformation: FieldAccessInformation, + project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider -) extends L1StringInterpreter { +) extends L1StringInterpreter[State] { override protected val cfg: CFG[Stmt[V], TACStmts[V]] = state.tac.cfg override type T = FieldRead[V] + /** + * 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 = { + val threshold = + try { + project.config.getInt(L1StringAnalysis.FieldWriteThresholdConfigKey) + } catch { + case t: Throwable => + logOnce(Error( + "analysis configuration - l1 string analysis", + s"couldn't read: ${L1StringAnalysis.FieldWriteThresholdConfigKey}", + t + ))(project.logContext) + 10 + } + + logOnce(Info( + "analysis configuration - l1 string analysis", + "l1 string analysis uses a field write threshold of " + threshold + ))(project.logContext) + threshold + } + /** * Currently, fields are approximated using the following approach. If a field of a type not * supported by the [[L1StringAnalysis]] is passed, @@ -51,7 +85,7 @@ case class L1FieldReadInterpreter( * 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]]. */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): 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. @@ -63,7 +97,7 @@ case class L1FieldReadInterpreter( // Write accesses exceeds the threshold => approximate with lower bound val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField) - if (writeAccesses.length > state.fieldWriteThreshold) { + if (writeAccesses.length > fieldWriteThreshold) { return FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 552be62630..40ce1376f8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -45,11 +46,12 @@ import org.opalj.value.ValueInformation class L1InterpretationHandler( tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, + project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, - state: L1ComputationState, + implicit val state: L1ComputationState, contextProvider: ContextProvider -) extends InterpretationHandler(tac) { +) extends InterpretationHandler[L1ComputationState](tac) { /** * Processed the given definition site in an interprocedural fashion. @@ -60,7 +62,7 @@ class L1InterpretationHandler( override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): 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 @@ -136,7 +138,7 @@ class L1InterpretationHandler( expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { val r = new L1ArrayAccessInterpreter( cfg, this, @@ -199,7 +201,6 @@ class L1InterpretationHandler( cfg, this, ps, - state, params, contextProvider ).interpret(expr, defSite) @@ -279,6 +280,7 @@ class L1InterpretationHandler( state, ps, fieldAccessInformation, + project, declaredFields, contextProvider ).interpret(expr, defSite) @@ -295,12 +297,11 @@ class L1InterpretationHandler( private def processNonVirtualFunctionCall( expr: NonVirtualFunctionCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { val r = L1NonVirtualFunctionCallInterpreter( cfg, this, ps, - state, contextProvider ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { @@ -316,7 +317,7 @@ class L1InterpretationHandler( def processVirtualMethodCall( expr: VirtualMethodCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { val r = L1VirtualMethodCallInterpreter(cfg, this).interpret(expr, defSite) doInterimResultHandling(r, defSite) r @@ -407,6 +408,7 @@ object L1InterpretationHandler { def apply( tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, + project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, state: L1ComputationState, @@ -414,6 +416,7 @@ object L1InterpretationHandler { ): L1InterpretationHandler = new L1InterpretationHandler( tac, ps, + project, declaredFields, fieldAccessInformation, state, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 561f23c9ee..5ea642bc1c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for preparing [[NewArray]] expressions. @@ -24,12 +25,12 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -class L1NewArrayInterpreter( +class L1NewArrayInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState, + override protected val exprHandler: InterpretationHandler[State], + state: State, params: List[Seq[StringConstancyInformation]] -) extends L1StringInterpreter { +) extends L1StringInterpreter[State] { override type T = NewArray[V] @@ -39,7 +40,7 @@ class L1NewArrayInterpreter( * definition sites producing a refinable result will have to be handled later on to * not miss this information. */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { // Only support for 1-D arrays if (instr.counts.length != 1) { return FinalEP(instr, StringConstancyProperty.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 1aee2fea87..e8cf22411c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -15,6 +15,7 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[NonVirtualFunctionCall]]s in an interprocedural fashion. @@ -23,15 +24,16 @@ import org.opalj.fpcf.PropertyStore */ case class L1NonVirtualFunctionCallInterpreter( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, + override protected val exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, - state: L1ComputationState, contextProvider: ContextProvider -) extends L1StringInterpreter { +) extends L1StringInterpreter[L1ComputationState] { override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit + state: L1ComputationState + ): EOptionP[Entity, StringConstancyProperty] = { val methods = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) 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/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index b9992a98eb..552222dba7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[NonVirtualMethodCall]]s in an interprocedural fashion. @@ -20,11 +21,11 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -case class L1NonVirtualMethodCallInterpreter( +case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, - state: L1ComputationState -) extends L1StringInterpreter { + override protected val exprHandler: InterpretationHandler[State], + state: State +) extends L1StringInterpreter[State] { override type T = NonVirtualMethodCall[V] @@ -39,7 +40,7 @@ case class L1NonVirtualMethodCallInterpreter( * * For all other calls, an empty list will be returned at the moment. */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { case "" => interpretInit(instr, e) @@ -54,7 +55,9 @@ case class L1NonVirtualMethodCallInterpreter( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit(init: T, defSite: Integer): EOptionP[Entity, StringConstancyProperty] = { + private def interpretInit(init: T, defSite: Integer)(implicit + state: State + ): 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/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index b78752247c..f93e383d85 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -20,6 +20,7 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[StaticFunctionCall]]s in an interprocedural fashion. @@ -29,16 +30,18 @@ import org.opalj.fpcf.PropertyStore */ class L1StaticFunctionCallInterpreter( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, + override protected val exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, - state: L1ComputationState, + implicit val state: L1ComputationState, params: List[Seq[StringConstancyInformation]], contextProvider: ContextProvider -) extends L1StringInterpreter { +) extends L1StringInterpreter[L1ComputationState] { override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)( + implicit state: L1ComputationState + ): EOptionP[Entity, StringConstancyProperty] = { if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { processStringValueOf(instr) } else { @@ -54,7 +57,9 @@ class L1StaticFunctionCallInterpreter( * 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]): EOptionP[Entity, StringConstancyProperty] = { + private def processStringValueOf(call: StaticFunctionCall[V])( + implicit state: L1ComputationState + ): EOptionP[Entity, StringConstancyProperty] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_, params) } val interim = results.find(_.isRefinable) if (interim.isDefined) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 3c73cac7d9..a03138f63c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -8,16 +8,16 @@ package l1 package interpretation import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** * @author Maximilian Rüsch */ -trait L1StringInterpreter extends StringInterpreter { - - override protected val exprHandler: L1InterpretationHandler +trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { /** * @param instr The instruction that is to be interpreted. It is the responsibility of implementations to make sure @@ -35,5 +35,17 @@ trait L1StringInterpreter extends StringInterpreter { * 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, defSite: Int): EOptionP[Entity, StringConstancyProperty] + def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] + + protected def handleInterpretationResult(ep: EOptionP[Entity, StringConstancyProperty])(implicit + state: State + ): Option[StringConstancyInformation] = { + ep match { + case FinalP(p) => + Some(p.stringConstancyInformation) + case eps => + state.dependees = eps :: state.dependees + None + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 491b916678..c9d656e938 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -33,12 +33,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ class L1VirtualFunctionCallInterpreter( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler, + override protected val exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, - state: L1ComputationState, params: List[Seq[StringConstancyInformation]], contextProvider: ContextProvider -) extends L1StringInterpreter { +) extends L1StringInterpreter[L1ComputationState] { override type T = VirtualFunctionCall[V] @@ -67,24 +66,26 @@ class L1VirtualFunctionCallInterpreter( * * @note This function takes care of updating [[state.fpe2sci]] as necessary. */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit + state: L1ComputationState + ): EOptionP[Entity, StringConstancyProperty] = { val result = instr.name match { - case "append" => interpretAppendCall(instr, defSite) + case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) - case "replace" => interpretReplaceCall(instr) + case "replace" => Some(interpretReplaceCall) case _ => instr.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => interpretArbitraryCall(instr, defSite) case _ => - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) + Some(StringConstancyInformation.lb) } } - if (result.isFinal) { - state.appendToFpe2Sci(defSite, result.asFinal.p.stringConstancyInformation) + if (result.isDefined) { + state.appendToFpe2Sci(defSite, result.get) } - result + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(result.getOrElse(StringConstancyInformation.lb))) } /** @@ -93,11 +94,13 @@ class L1VirtualFunctionCallInterpreter( * 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): EOptionP[Entity, StringConstancyProperty] = { + private def interpretArbitraryCall(instr: T, defSite: Int)( + implicit state: L1ComputationState + ): Option[StringConstancyInformation] = { val (methods, _) = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) if (methods.isEmpty) { - return FinalEP(instr, StringConstancyProperty.lb) + return Some(StringConstancyInformation.lb) } // TODO: Type Iterator! val directCallSites = state.callees.directCallSites(NoContext)(ps, contextProvider) @@ -128,7 +131,7 @@ class L1VirtualFunctionCallInterpreter( val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (nonFinalResults.nonEmpty) { state.nonFinalFunctionArgs(instr) = params - return nonFinalResults.head + return None } state.nonFinalFunctionArgs.remove(instr) @@ -148,12 +151,11 @@ class L1VirtualFunctionCallInterpreter( val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) L1StringAnalysis.registerParams(entity, evaluatedParams) - val eps = ps(entity, StringConstancyProperty.key) - eps match { + ps(entity, StringConstancyProperty.key) match { case r: FinalEP[SContext, StringConstancyProperty] => state.appendToFpe2Sci(defSite, r.p.stringConstancyInformation) r - case _ => + case eps => state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) eps @@ -168,11 +170,11 @@ class L1VirtualFunctionCallInterpreter( } val finalResults = results.filter(_.isFinal) - val intermediateResults = results.filter(_.isRefinable) + // val intermediateResults = results.filter(_.isRefinable) if (results.length == finalResults.length) { - finalResults.head + Some(finalResults.head.asFinal.p.stringConstancyInformation) } else { - intermediateResults.head + None } } @@ -181,22 +183,22 @@ class L1VirtualFunctionCallInterpreter( * 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], - defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val receiverResults = receiverValuesOfAppendCall(appendCall, state) - val appendResult = valueOfAppendCall(appendCall, state) + private def interpretAppendCall(appendCall: VirtualFunctionCall[V])( + implicit state: L1ComputationState + ): Option[StringConstancyInformation] = { + val receiverResults = receiverValuesOfAppendCall(appendCall) + val appendResult = valueOfAppendCall(appendCall) - // If there is an intermediate result, return this one (then the final result cannot yet be - // computed) + // If there is an intermediate result, return this one (then the final result cannot yet be computed) if (receiverResults.head.isRefinable) { - return receiverResults.head + return None } else if (appendResult.isRefinable) { - return appendResult + return None } - val receiverScis = receiverResults.map { _.asFinal.p.stringConstancyInformation } + val receiverScis = receiverResults.map { + _.asFinal.p.stringConstancyInformation + } val appendSci = appendResult.asFinal.p.stringConstancyInformation // The case can occur that receiver and append value are empty; although, it is @@ -226,8 +228,7 @@ class L1VirtualFunctionCallInterpreter( ) } - val e: Integer = defSite - FinalEP(e, StringConstancyProperty(finalSci)) + Some(finalSci) } /** @@ -241,9 +242,8 @@ class L1VirtualFunctionCallInterpreter( * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V], - state: L1ComputationState - ): List[EOptionP[Entity, StringConstancyProperty]] = { + call: VirtualFunctionCall[V] + )(implicit state: L1ComputationState): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted val allResults = defSites.map(ds => (ds, exprHandler.processDefSite(ds, params))) @@ -272,9 +272,8 @@ class L1VirtualFunctionCallInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], - state: L1ComputationState - ): EOptionP[Entity, StringConstancyProperty] = { + call: VirtualFunctionCall[V] + )(implicit state: L1ComputationState): 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 @@ -347,17 +346,18 @@ class L1VirtualFunctionCallInterpreter( * 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]): EOptionP[Entity, StringConstancyProperty] = - // TODO: Can it produce an intermediate result??? - exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) + private def interpretToStringCall(call: VirtualFunctionCall[V])( + implicit state: L1ComputationState + ): Option[StringConstancyInformation] = + handleInterpretationResult(exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params)) /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. * (Currently, this function simply approximates `replace` functions by returning the lower * bound of [[StringConstancyProperty]]). */ - private def interpretReplaceCall(instr: VirtualFunctionCall[V]): FinalEP[Entity, StringConstancyProperty] = - FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) + private def interpretReplaceCall: StringConstancyInformation = + InterpretationHandler.getStringConstancyInformationForReplace /** * Checks whether a given string is an integer value, i.e. contains only numbers. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 97cd13e135..cc95a840ec 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -13,6 +13,7 @@ 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.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[VirtualMethodCall]]s in an interprocedural fashion. @@ -20,10 +21,10 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -case class L1VirtualMethodCallInterpreter( +case class L1VirtualMethodCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: L1InterpretationHandler -) extends L1StringInterpreter { + override protected val exprHandler: InterpretationHandler[State] +) extends L1StringInterpreter[State] { override type T = VirtualMethodCall[V] @@ -39,7 +40,7 @@ case class L1VirtualMethodCallInterpreter( * * For all other calls, an empty list will be returned. */ - override def interpret(instr: T, defSite: Int): FinalEP[T, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): FinalEP[T, StringConstancyProperty] = { val sci = instr.name match { case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) case _ => StringConstancyInformation.getNeutralElement 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 d2cda2c867..9ff969682d 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,7 +32,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -class PathTransformer(val interpretationHandler: InterpretationHandler) { +class PathTransformer[State <: ComputationState[State]](val interpretationHandler: InterpretationHandler[State]) { /** * Accumulator function for transforming a path into a StringTree element. @@ -40,7 +40,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { private def pathToTreeAcc( subpath: SubPath, fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] - ): Option[StringTree] = { + )(implicit state: State): Option[StringTree] = { subpath match { case fpe: FlatPathElement => val sci = if (fpe2Sci.contains(fpe.element)) { @@ -135,7 +135,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { path: Path, fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] = Map.empty, resetExprHandler: Boolean = true - ): StringTree = { + )(implicit state: State): StringTree = { val tree = path.elements.size match { case 1 => // It might be that for some expressions, a neutral element is produced which is From 8bd2657c1bbad63133fbb7c05f991ecd2f62a6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 8 Feb 2024 16:22:50 +0100 Subject: [PATCH 351/583] Refactor dependent vars handling --- .../string_analysis/StringAnalysis.scala | 84 +++++++++---------- .../string_analysis/l0/L0StringAnalysis.scala | 59 ++++++------- .../string_analysis/l1/L1StringAnalysis.scala | 52 ++++-------- 3 files changed, 87 insertions(+), 108 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 0a50d24f23..5bf7cd1728 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -54,7 +54,7 @@ trait StringAnalysis extends FPCFAnalysis { * 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: State): InterimResult[StringConstancyProperty] = InterimResult( + protected def getInterimResult(state: State): InterimResult[StringConstancyProperty] = InterimResult( state.entity, computeNewLowerBound(state), computeNewUpperBound(state), @@ -153,6 +153,12 @@ trait StringAnalysis extends FPCFAnalysis { if (state.isSetupCompleted && state.parameterDependeesCount == 0) { processFinalP(state, eps.e, p) + // No more dependees => Return the result for this analysis run + if (state.dependees.isEmpty) { + computeFinalResult(state) + } else { + getInterimResult(state) + } } else { determinePossibleStrings(state) } @@ -193,7 +199,7 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - private def computeFinalResult(state: State): Result = { + protected def computeFinalResult(state: State): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, @@ -208,19 +214,13 @@ trait StringAnalysis extends FPCFAnalysis { state: State, e: Entity, p: StringConstancyProperty - ): ProperPropertyComputationResult = { + ): Unit = { // Add mapping information (which will be used for computing the final result) state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { state.appendToFpe2Sci(_, p.stringConstancyInformation) } state.dependees = state.dependees.filter(_.e != e) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } } /** @@ -343,46 +343,46 @@ trait StringAnalysis extends FPCFAnalysis { } /** - * 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. + * Finds [[PUVar]]s the string constancy information computation for the given [[Path]] depends on. Enables passing + * an entity to ignore (usually the entity for which the path was created so it does not depend on itself. + * + * @return A mapping from dependent [[PUVar]]s to the [[FlatPathElement]] indices they occur in. */ - private def findDependeesAcc(subpath: SubPath, stmts: Array[Stmt[V]])( - implicit state: State - ): ListBuffer[(SEntity, Int)] = { - val foundDependees = ListBuffer[(SEntity, 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.filter(_ >= 0).foreach { ds => - val expr = stmts(ds).asAssignment.expr - // TODO check support for passing nested string builder directly (e.g. with a test case) - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append((param.toPersistentForm(state.tac.stmts), fpe.element)) + protected def findDependentVars(path: Path, ignore: SEntity)( // We may need to register the old path with them + implicit state: State): mutable.LinkedHashMap[SEntity, Int] = { + val stmts = state.tac.stmts + + def findDependeesAcc(subpath: SubPath): ListBuffer[(SEntity, Int)] = { + val foundDependees = ListBuffer[(SEntity, 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.filter(_ >= 0).foreach { ds => + val expr = stmts(ds).asAssignment.expr + // TODO check support for passing nested string builder directly (e.g. with a test case) + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + foundDependees.append((param.toPersistentForm(stmts), fpe.element)) + } } } - } - case _ => - } - foundDependees - case npe: NestedPathElement => - npe.element.foreach { nextSubpath => foundDependees.appendAll(findDependeesAcc(nextSubpath, stmts)) } - foundDependees - case _ => foundDependees + case _ => + } + foundDependees + case npe: NestedPathElement => + foundDependees.appendAll(npe.element.flatMap { findDependeesAcc }) + foundDependees + case _ => foundDependees + } } - } - protected def findDependentVars(path: Path, stmts: Array[Stmt[V]], ignore: SEntity)( - implicit state: State - ): mutable.LinkedHashMap[SEntity, Int] = { val dependees = mutable.LinkedHashMap[SEntity, Int]() path.elements.foreach { nextSubpath => - findDependeesAcc(nextSubpath, stmts).foreach { nextPair => + findDependeesAcc(nextSubpath).foreach { nextPair => if (ignore != nextPair._1) { dependees.put(nextPair._1, nextPair._2) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 775650bcaa..adc68bf082 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -15,6 +15,7 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult @@ -26,7 +27,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler -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 @@ -60,7 +60,8 @@ protected[l0] case class L0ComputationState( * (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]]. + * transformed into a string tree by making use of a + * [[org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer]]. * * @author Patrick Mell */ @@ -89,9 +90,6 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { implicit val _state: State = state - // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyInformation.lb - implicit val tac: TACode[TACMethodParameter, V] = state.tac val stmts = tac.stmts @@ -113,46 +111,45 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } val path = new WindowPathFinder(tac.cfg).findPaths(initDefSites, uVar.definedBy.head) - val leanPath = path.makeLeanPath(uVar, stmts) + state.computedLeanPath = path.makeLeanPath(uVar, stmts) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPath, stmts, puVar) + val dependentVars = findDependentVars(state.computedLeanPath, puVar) if (dependentVars.nonEmpty) { - state.computedLeanPath = leanPath dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } - dependentVars.keys.foreach { nextVar => propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { - case finalEP: FinalEP[SContext, StringConstancyProperty] => - return processFinalP(state, finalEP.e, finalEP.p) + case FinalEP(e, p) => + // Add mapping information (which will be used for computing the final result) + state.var2IndexMapping(e._1).foreach { + state.appendToFpe2Sci(_, p.stringConstancyInformation) + } + state.dependees = state.dependees.filter(_.e.asInstanceOf[SContext] != e) case ep => state.dependees = ep :: state.dependees } } + } + + if (state.dependees.isEmpty) { + computeFinalResult(state) } else { - val stringTree = new PathTransformer(L0InterpretationHandler(tac)).pathToStringTree(leanPath) - sci = stringTree.reduce(true) + getInterimResult(state) } } else { - // We deal with pure strings + // We deal with pure strings TODO unify result handling val interpretationHandler = L0InterpretationHandler(tac) - sci = StringConstancyInformation.reduceMultiple( + val sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => interpretationHandler.processDefSite(ds).asFinal.p.stringConstancyInformation } ) - } - if (state.dependees.nonEmpty) { - InterimResult( - state.entity._1, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - state.dependees.toSet, - continuation(state) - ) - } else { - Result(state.entity, StringConstancyProperty(sci)) + if (state.dependees.isEmpty) { + Result(state.entity, StringConstancyProperty(sci)) + } else { + getInterimResult(state) + } } } @@ -166,9 +163,13 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis override protected def continuation( state: State )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case finalEP: FinalEP[_, _] => - val finalScpEP = finalEP.asInstanceOf[FinalEP[SContext, StringConstancyProperty]] - processFinalP(state, finalScpEP.e, finalScpEP.p) + case FinalEP(e: Entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + processFinalP(state, e, p) + if (state.dependees.isEmpty) { + computeFinalResult(state) + } else { + getInterimResult(state) + } case InterimLUBP(lb, ub) => InterimResult(state.entity, lb, ub, state.dependees.toSet, continuation(state)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 23d32256a0..ded9abe352 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -30,7 +30,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore @@ -108,31 +107,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { protected implicit val fieldAccessInformation: FieldAccessInformation = project.get(FieldAccessInformationKey) protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) - /** - * 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: State): InterimResult[StringConstancyProperty] = InterimResult( - state.entity, - computeNewLowerBound(state), - computeNewUpperBound(state), - state.dependees.toSet, - continuation(state) - ) - - private def computeNewUpperBound(state: State): StringConstancyProperty = { - if (state.computedLeanPath != null) { - StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( - state.computedLeanPath, - state.interimFpe2sci - )(state).reduce(true)) - } else { - StringConstancyProperty.lb - } - } - - private def computeNewLowerBound(state: State): StringConstancyProperty = StringConstancyProperty.lb - def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) val state = L1ComputationState(dm, data) @@ -162,7 +136,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { /** * 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. + * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. */ override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { val puVar = state.entity._1 @@ -273,25 +247,29 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } val call = stmts(defSites.head).asAssignment.expr - var attemptFinalResultComputation = false + var attemptFinalResultComputation = true if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(state.computedLeanPath, stmts, puVar)(state) + // Find DUVars that the analysis of the current entity depends on + val dependentVars = findDependentVars(state.computedLeanPath, puVar)(state) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar => dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) ep match { - case FinalEP(e, p) => return processFinalP(state, e, p) - case _ => state.dependees = ep :: state.dependees + case FinalEP(e, p) => + processFinalP(state, e, p) + // No more dependees => Return the result for this analysis run + if (state.dependees.isEmpty) { + return computeFinalResult(state) + } else { + return getInterimResult(state) + } + case _ => + state.dependees = ep :: state.dependees + attemptFinalResultComputation = false } } - } else { - attemptFinalResultComputation = true } - } else { - // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings - attemptFinalResultComputation = true } var sci = StringConstancyInformation.lb From c1e92099e5868e65ab46ac23c90c75442b4ab90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 8 Feb 2024 16:30:56 +0100 Subject: [PATCH 352/583] Simplify lean path computation --- .../string_analysis/StringAnalysis.scala | 21 +++++++------------ .../string_analysis/l0/L0StringAnalysis.scala | 13 +++++------- .../string_analysis/l1/L1StringAnalysis.scala | 4 ++-- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 5bf7cd1728..6cdec036d9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -26,7 +26,6 @@ import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter -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.NestedPathType @@ -260,21 +259,16 @@ trait StringAnalysis extends FPCFAnalysis { } /** - * This function is a wrapper function for [[computeLeanPathForStringConst]] and - * [[computeLeanPathForStringBuilder]]. + * Wrapper function for [[computeLeanPathForStringConst]] and [[computeLeanPathForStringBuilder]]. */ - protected def computeLeanPath( - value: V, - tac: TACode[TACMethodParameter, V] - ): Path = { + protected def computeLeanPath(value: V, tac: TACode[TACMethodParameter, V]): Path = { val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { computeLeanPathForStringConst(value) } else { val call = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (leanPath, _) = computeLeanPathForStringBuilder(value, tac) - leanPath + computeLeanPathForStringBuilder(value, tac).get } else { computeLeanPathForStringConst(value) } @@ -310,14 +304,13 @@ trait StringAnalysis extends FPCFAnalysis { protected def computeLeanPathForStringBuilder( value: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): (Path, Boolean) = { - val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) + ): Option[Path] = { val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) if (initDefSites.isEmpty) { - (null, false) + None } else { - val paths = pathFinder.findPaths(initDefSites, value.definedBy.toArray.max) - (paths.makeLeanPath(value, tac.stmts), true) + val paths = new WindowPathFinder(tac.cfg).findPaths(initDefSites, value.definedBy.toArray.max) + Some(paths.makeLeanPath(value, tac.stmts)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index adc68bf082..d1f37dc3ad 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -27,7 +27,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.fpcf.properties.TACAI /** @@ -102,16 +101,14 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis return Result(state.entity, StringConstancyProperty.lb) } - val call = stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit(uVar, stmts) - if (initDefSites.isEmpty) { + val expr = stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + val leanPath = computeLeanPathForStringBuilder(uVar, tac) + if (leanPath.isEmpty) { // String{Builder,Buffer} from method parameter is to be evaluated return Result(state.entity, StringConstancyProperty.lb) } - - val path = new WindowPathFinder(tac.cfg).findPaths(initDefSites, uVar.definedBy.head) - state.computedLeanPath = path.makeLeanPath(uVar, stmts) + state.computedLeanPath = leanPath.get // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, puVar) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index ded9abe352..fc3f6d8342 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -202,8 +202,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } else { val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (_, hasInitDefSites) = computeLeanPathForStringBuilder(uVar, state.tac) - if (!hasInitDefSites) { + val leanPath = computeLeanPathForStringBuilder(uVar, state.tac) + if (leanPath.isEmpty) { return Result(state.entity, StringConstancyProperty.lb) } val hasSupportedParamType = state.entity._2.parameterTypes.exists { From c4deda8c2b9e7995d3abbb92cc0e067a941f8598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 8 Feb 2024 20:43:56 +0100 Subject: [PATCH 353/583] Refactor path elements to hold pcs instead of def sites --- .../IntraProceduralTestMethods.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 21 +-- .../string_analysis/ComputationState.scala | 63 +++++---- .../string_analysis/StringAnalysis.scala | 95 ++++++-------- .../InterpretationHandler.scala | 10 +- .../interpretation/StringInterpreter.scala | 5 +- .../string_analysis/l0/L0StringAnalysis.scala | 6 +- .../L0InterpretationHandler.scala | 4 +- .../string_analysis/l1/L1StringAnalysis.scala | 79 ++---------- .../l1/finalizer/ArrayLoadFinalizer.scala | 12 +- .../NonVirtualMethodCallFinalizer.scala | 12 +- .../StaticFunctionCallFinalizer.scala | 13 +- .../VirtualFunctionCallFinalizer.scala | 47 +++---- .../L1ArrayAccessInterpreter.scala | 17 +-- .../L1FieldReadInterpreter.scala | 4 +- .../L1InterpretationHandler.scala | 55 +++++--- .../L1NewArrayInterpreter.scala | 6 +- .../L1NonVirtualMethodCallInterpreter.scala | 6 +- .../L1StaticFunctionCallInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 11 +- .../preprocessing/AbstractPathFinder.scala | 27 ++-- .../preprocessing/DefaultPathFinder.scala | 10 +- .../string_analysis/preprocessing/Path.scala | 120 +++++++++--------- .../preprocessing/PathTransformer.scala | 10 +- .../preprocessing/WindowPathFinder.scala | 12 +- .../string_analysis/string_analysis.scala | 3 +- .../main/scala/org/opalj/tac/package.scala | 26 ++-- 27 files changed, 313 insertions(+), 365 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index d3c236e1d7..1eb242f507 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -737,7 +737,7 @@ public void withException(String filename) { value = "case with a try-catch-finally exception", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" 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 e9b56f6a8d..72e319ce79 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 @@ -133,17 +133,20 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { val newEntities = entities //.filter(entity => entity._2.name.startsWith("tryCatchFinally")) //.filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - //.filterNot(entity => entity._2.name.startsWith("switchNested")) - //.filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - //.filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) - //.filterNot(entity => entity._2.name.startsWith("simpleStringConcat")) - //.filterNot(entity => entity._2.name.startsWith("multipleDefSites")) - //.filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) - newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + .filterNot(entity => entity._2.name.startsWith("switchNested")) + .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) + .filterNot(entity => entity._2.name.startsWith("simpleStringConcat")) + .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) + .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) - as.propertyStore.shutdown() + //it("can be executed without exceptions") { + newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + + as.propertyStore.shutdown() - validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) + validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) + //} } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 6120738792..316b8d51d4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -17,7 +17,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path -import org.opalj.value.ValueInformation /** * This class is to be used to store state information that are required at a later point in @@ -35,7 +34,7 @@ trait ComputationState[State <: ComputationState[State]] { /** * The Three-Address Code of the entity's method */ - var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ // TODO add dm to tac dependee mapping + var tac: TAC = _ // TODO add dm to tac dependee mapping /** * The interpretation handler to use for computing a final result (if possible). @@ -60,18 +59,17 @@ trait ComputationState[State <: ComputationState[State]] { 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 values of the FlatPathElements */ val var2IndexMapping: mutable.Map[SEntity, ListBuffer[Int]] = mutable.Map() /** - * A mapping from values / indices of FlatPathElements to StringConstancyInformation + * A mapping from values of FlatPathElements to StringConstancyInformation */ val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() /** - * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which are - * not yet final. + * A mapping from a value of a FlatPathElement to StringConstancyInformation which are not yet final. */ val interimFpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() @@ -145,20 +143,19 @@ trait ComputationState[State <: ComputationState[State]] { val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[V], Boolean] = mutable.Map() /** - * 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 - * the list of `defSite`. + * Takes a `pc` as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] map accordingly, however, only + * if `pc` is not yet present or `sci` not present within the list of `pc`. */ def appendToFpe2Sci( - defSite: Int, - sci: StringConstancyInformation, - reset: Boolean = false + pc: Int, + sci: StringConstancyInformation, + reset: Boolean = false ): Unit = { - if (reset || !fpe2sci.contains(defSite)) { - fpe2sci(defSite) = ListBuffer() + if (reset || !fpe2sci.contains(pc)) { + fpe2sci(pc) = ListBuffer() } - if (!fpe2sci(defSite).contains(sci)) { - fpe2sci(defSite).append(sci) + if (!fpe2sci(pc).contains(sci)) { + fpe2sci(pc).append(sci) } } @@ -182,50 +179,50 @@ trait ComputationState[State <: ComputationState[State]] { * 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 pc 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, - entity: Option[SEntity] = None + pc: Int, + sci: StringConstancyInformation, + entity: Option[SEntity] = None ): Unit = { - val numElements = var2IndexMapping.values.flatten.count(_ == defSite) + val numElements = var2IndexMapping.values.flatten.count(_ == pc) var addedNewList = false - if (!interimFpe2sci.contains(defSite)) { - interimFpe2sci(defSite) = ListBuffer() + if (!interimFpe2sci.contains(pc)) { + interimFpe2sci(pc) = ListBuffer() addedNewList = true } // Append an element - val containsSci = interimFpe2sci(defSite).contains(sci) + val containsSci = interimFpe2sci(pc).contains(sci) if (!containsSci && entity.isEmpty) { - if (!addedNewList && interimFpe2sci(defSite).length == numElements) { - interimFpe2sci(defSite).remove(0) + if (!addedNewList && interimFpe2sci(pc).length == numElements) { + interimFpe2sci(pc).remove(0) } - interimFpe2sci(defSite).append(sci) + interimFpe2sci(pc).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) + if (interimFpe2sci(pc).nonEmpty) { + interimFpe2sci(pc).remove(0) } - interimFpe2sci(defSite).append(sci) + interimFpe2sci(pc).append(sci) } } } /** - * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. + * Takes an entity as well as a pc and append it to [[var2IndexMapping]]. */ - def appendToVar2IndexMapping(entity: SEntity, defSite: Int): Unit = { + def appendToVar2IndexMapping(entity: SEntity, pc: Int): Unit = { if (!var2IndexMapping.contains(entity)) { var2IndexMapping(entity) = ListBuffer() } - var2IndexMapping(entity).append(defSite) + var2IndexMapping(entity).append(pc) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 6cdec036d9..2314371231 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -25,7 +25,6 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter 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.NestedPathType @@ -34,12 +33,11 @@ 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.value.ValueInformation /** * String Analysis trait defining some basic dependency handling. * - * @author Patrick Mell + * @author Maximilian Rüsch */ trait StringAnalysis extends FPCFAnalysis { @@ -144,7 +142,7 @@ trait StringAnalysis extends FPCFAnalysis { // defer this computations when we know that all necessary // information are available state.entity2Function.clear() - if (!computeResultsForPath(state.computedLeanPath, state)) { + if (!computeResultsForPath(state.computedLeanPath)(state)) { return determinePossibleStrings(state) } } @@ -223,35 +221,30 @@ trait StringAnalysis extends FPCFAnalysis { } /** - * This function traverses the given path, computes all string values along the path and stores these information in - * the given state. + * 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 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. */ - private def computeResultsForPath( - p: Path, - state: State - ): Boolean = { + protected def computeResultsForPath(p: Path)(implicit state: State): Boolean = { var hasFinalResult = true - p.elements.foreach { - case FlatPathElement(index) => - if (!state.fpe2sci.contains(index)) { - val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq))(state) + case fpe: FlatPathElement => + if (!state.fpe2sci.contains(fpe.pc)) { + val eOptP = state.iHandler.processDefSite( + valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get, + state.params.toList.map(_.toSeq) + ) if (eOptP.isFinal) { - state.appendToFpe2Sci(index, eOptP.asFinal.p.stringConstancyInformation, reset = true) + state.appendToFpe2Sci(fpe.pc, eOptP.asFinal.p.stringConstancyInformation, reset = true) } else { hasFinalResult = false } } case npe: NestedPathElement => - val subFinalResult = computeResultsForPath( - Path(npe.element.toList), - state - ) - hasFinalResult = hasFinalResult && subFinalResult + hasFinalResult = hasFinalResult && computeResultsForPath(Path(npe.element.toList)) case _ => } @@ -261,16 +254,16 @@ trait StringAnalysis extends FPCFAnalysis { /** * Wrapper function for [[computeLeanPathForStringConst]] and [[computeLeanPathForStringBuilder]]. */ - protected def computeLeanPath(value: V, tac: TACode[TACMethodParameter, V]): Path = { + protected def computeLeanPath(value: V)(implicit tac: TAC): Path = { val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { - computeLeanPathForStringConst(value) + computeLeanPathForStringConst(value)(tac.stmts) } else { val call = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - computeLeanPathForStringBuilder(value, tac).get + computeLeanPathForStringBuilder(value).get } else { - computeLeanPathForStringConst(value) + computeLeanPathForStringConst(value)(tac.stmts) } } } @@ -278,18 +271,16 @@ trait StringAnalysis extends FPCFAnalysis { /** * This function computes the lean path for a [[V]] which is required to be a string expression. */ - protected def computeLeanPathForStringConst(value: V): Path = { + protected def computeLeanPathForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Path = { val defSites = value.definedBy.toArray.sorted - if (defSites.length == 1) { - // Trivial case for just one element - Path(List(FlatPathElement(defSites.head))) + val element = if (defSites.length == 1) { + 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)))) + // Create alternative branches with intermediate None-Type nested path elements + val children = defSites.map { ds => NestedPathElement(ListBuffer(FlatPathElement(ds)), None) } + NestedPathElement(ListBuffer.from(children), Some(NestedPathType.CondWithAlternative)) } + Path(List(element)) } /** @@ -301,43 +292,41 @@ trait StringAnalysis extends FPCFAnalysis { * 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)`. */ - protected def computeLeanPathForStringBuilder( - value: V, - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] - ): Option[Path] = { + protected def computeLeanPathForStringBuilder(value: V)(implicit tac: TAC): Option[Path] = { val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) if (initDefSites.isEmpty) { None } else { - val paths = new WindowPathFinder(tac.cfg).findPaths(initDefSites, value.definedBy.toArray.max) - Some(paths.makeLeanPath(value, tac.stmts)) + val path = WindowPathFinder(tac).findPaths(initDefSites, value.definedBy.toArray.max) + val leanPath = path.makeLeanPath(value) + Some(leanPath) } } - private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { - def hasExprFormalParamUsage(expr: Expr[V]): Boolean = expr match { - case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) - case duVar: V => duVar.definedBy.exists(_ < 0) - case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) - case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) - case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) - case _ => false - } + protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { + case duVar: V => duVar.definedBy.exists(_ < 0) + case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) + case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) + case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) + case _ => false + } + protected def hasFormalParamUsageAlongPath(path: Path)(implicit tac: TAC): Boolean = { + implicit val pcToIndex: Array[Int] = tac.pcToIndex path.elements.exists { - case FlatPathElement(index) => stmts(index) match { + case FlatPathElement(index) => tac.stmts(index) match { case Assignment(_, _, expr) => hasExprFormalParamUsage(expr) case ExprStmt(_, expr) => hasExprFormalParamUsage(expr) case _ => false } - case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(Path(subPath.toList), stmts) + case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(Path(subPath.toList)) case _ => false } } /** * Finds [[PUVar]]s the string constancy information computation for the given [[Path]] depends on. Enables passing - * an entity to ignore (usually the entity for which the path was created so it does not depend on itself. + * an entity to ignore (usually the entity for which the path was created so it does not depend on itself). * * @return A mapping from dependent [[PUVar]]s to the [[FlatPathElement]] indices they occur in. */ @@ -351,7 +340,7 @@ trait StringAnalysis extends FPCFAnalysis { 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 { + stmts(fpe.stmtIndex(state.tac.pcToIndex)) match { case ExprStmt(_, outerExpr) => if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { val param = outerExpr.asVirtualFunctionCall.params.head.asVar @@ -359,7 +348,7 @@ trait StringAnalysis extends FPCFAnalysis { val expr = stmts(ds).asAssignment.expr // TODO check support for passing nested string builder directly (e.g. with a test case) if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append((param.toPersistentForm(stmts), fpe.element)) + foundDependees.append((param.toPersistentForm(stmts), fpe.pc)) } } } 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 dd740e30a5..a2181e64b7 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,15 +18,11 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis -import org.opalj.value.ValueInformation -abstract class InterpretationHandler[State <: ComputationState[State]](tac: TACode[ - TACMethodParameter, - DUVar[ValueInformation] -]) { +abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) { - protected val stmts: Array[Stmt[DUVar[ValueInformation]]] = tac.stmts - protected val cfg: CFG[Stmt[DUVar[ValueInformation]], TACStmts[DUVar[ValueInformation]]] = tac.cfg + protected val stmts: Array[Stmt[V]] = tac.stmts + protected val cfg: CFG[Stmt[V], TACStmts[V]] = tac.cfg /** * A list of definition sites that have already been processed. Store it as a map for constant diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 0ee5908c87..a0a22eca27 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -22,7 +22,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.value.ValueInformation /** * @author Maximilian Rüsch @@ -61,7 +60,7 @@ trait StringInterpreter[State <: ComputationState[State]] { ps: PropertyStore, m: Method, s: State - ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { + ): (EOptionP[Method, TACAI], Option[TAC]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { (tacai, tacai.ub.tac) @@ -102,7 +101,7 @@ trait StringInterpreter[State <: ComputationState[State]] { */ protected def getParametersForPCs( pcs: Iterable[Int], - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + tac: TAC ): List[Seq[Expr[V]]] = { val paramLists = ListBuffer[Seq[Expr[V]]]() pcs.map(tac.pcToIndex).foreach { stmtIndex => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index d1f37dc3ad..5a5337c18f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -70,7 +70,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis def analyze(data: SContext): ProperPropertyComputationResult = { // Retrieve TAC from property store - val tacOpt: Option[TACode[TACMethodParameter, V]] = ps(data._2, TACAI.key) match { + val tacOpt: Option[TAC] = ps(data._2, TACAI.key) match { case UBP(tac) => if (tac.tac.isEmpty) None else Some(tac.tac.get) case _ => None } @@ -89,7 +89,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { implicit val _state: State = state - implicit val tac: TACode[TACMethodParameter, V] = state.tac + implicit val tac: TAC = state.tac val stmts = tac.stmts val puVar = state.entity._1 @@ -103,7 +103,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis val expr = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - val leanPath = computeLeanPathForStringBuilder(uVar, tac) + val leanPath = computeLeanPathForStringBuilder(uVar) if (leanPath.isEmpty) { // String{Builder,Buffer} from method parameter is to be evaluated return Result(state.entity, StringConstancyProperty.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 60b75ccfff..a7caea5a97 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -31,7 +31,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt * @author Maximilian Rüsch */ class L0InterpretationHandler( - tac: TACode[TACMethodParameter, V] + tac: TAC ) extends InterpretationHandler[L0ComputationState](tac) { /** @@ -95,5 +95,5 @@ class L0InterpretationHandler( object L0InterpretationHandler { - def apply(tac: TACode[TACMethodParameter, V]): L0InterpretationHandler = new L0InterpretationHandler(tac) + def apply(tac: TAC): L0InterpretationHandler = new L0InterpretationHandler(tac) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index fc3f6d8342..fe093a3ccc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -150,7 +150,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val stmts = state.tac.stmts if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar, state.tac) + state.computedLeanPath = computeLeanPath(uVar)(state.tac) } if (state.iHandler == null) { @@ -202,7 +202,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } else { val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val leanPath = computeLeanPathForStringBuilder(uVar, state.tac) + val leanPath = computeLeanPathForStringBuilder(uVar)(state.tac) if (leanPath.isEmpty) { return Result(state.entity, StringConstancyProperty.lb) } @@ -210,7 +210,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { L1StringAnalysis.isSupportedType } if (hasSupportedParamType) { - hasFormalParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) + hasFormalParamUsageAlongPath(state.computedLeanPath)(state.tac) } else { !hasCallersOrParamInfo } @@ -275,16 +275,16 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { var sci = StringConstancyInformation.lb if (attemptFinalResultComputation && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath, state) + && computeResultsForPath(state.computedLeanPath)(state) ) { // 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 fpe: FlatPathElement => + state.fpe2sci.contains(fpe.pc) && state.fpe2sci(fpe.pc).length == 1 && + state.fpe2sci(fpe.pc).head == StringConstancyInformation.getNeutralElement case _ => false } } else false @@ -347,9 +347,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state: State, iHandler: InterpretationHandler[State] ): Unit = path.elements.foreach { - case FlatPathElement(index) => - if (!state.fpe2sci.contains(index)) { - iHandler.finalizeDefSite(index, state) + case fpe: FlatPathElement => + if (!state.fpe2sci.contains(fpe.pc)) { + iHandler.finalizeDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get, state) } case npe: NestedPathElement => finalizePreparations(Path(npe.element.toList), state, iHandler) @@ -423,62 +423,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { !hasIntermediateResult } - /** - * 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 state The current state of the computation. This function will alter - * [[L1ComputationState.fpe2sci]]. - * @return Returns `true` if all values computed for the path are final results. - */ - private def computeResultsForPath( - p: Path, - state: L1ComputationState - ): Boolean = { - var hasFinalResult = true - - p.elements.foreach { - case FlatPathElement(index) => - if (!state.fpe2sci.contains(index)) { - val eOptP = state.iHandler.processDefSite(index, state.params.toList.map(_.toSeq))(state) - if (eOptP.isFinal) { - state.appendToFpe2Sci(index, eOptP.asFinal.p.stringConstancyInformation, reset = true) - } else { - hasFinalResult = false - } - } - case npe: NestedPathElement => - val subFinalResult = computeResultsForPath( - Path(npe.element.toList), - state - ) - hasFinalResult = hasFinalResult && subFinalResult - case _ => - } - - hasFinalResult - } - - private def hasFormalParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { - def hasExprFormalParamUsage(expr: Expr[V]): Boolean = expr match { - case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) - case duVar: V => duVar.definedBy.exists(_ < 0) - case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) - case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) - case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) - case _ => false - } - - path.elements.exists { - case FlatPathElement(index) => stmts(index) match { - case Assignment(_, _, expr) => hasExprFormalParamUsage(expr) - case ExprStmt(_, expr) => hasExprFormalParamUsage(expr) - case _ => false - } - case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(Path(subPath.toList), stmts) - case _ => false - } + override protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { + case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, tac.stmts).exists(_ < 0) + case _ => super.hasExprFormalParamUsage(expr) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala index 013c8403b4..9d79651d22 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala @@ -28,15 +28,15 @@ case class ArrayLoadFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) - - allDefSites.foreach { ds => - if (!state.fpe2sci.contains(ds)) { - state.iHandler.finalizeDefSite(ds, state) + val allDefSitesByPC = allDefSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + allDefSitesByPC.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(allDefSitesByPC(pc), state) } } - state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( - allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds => state.fpe2sci(ds) } + state.fpe2sci(pcOfDefSite(defSite)(state.tac.stmts)) = ListBuffer(StringConstancyInformation.reduceMultiple( + allDefSitesByPC.keys.filter(state.fpe2sci.contains).toList.sorted.flatMap { pc => state.fpe2sci(pc) } )) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala index 3412703224..87ee10a2cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala @@ -25,16 +25,16 @@ case class NonVirtualMethodCallFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { 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 defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + defSitesByPC.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(defSitesByPC(pc), state) } } - val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } - StringConstancyInformation.reduceMultiple(scis.flatten.toList) + StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) } else { StringConstancyInformation.lb } - state.appendToFpe2Sci(defSite, toAppend, reset = true) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), toAppend, reset = true) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala index 2fb89d6c76..5373f4fdaf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala @@ -30,17 +30,16 @@ case class StaticFunctionCallFinalizer( // 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 defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + defSitesByPC.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(defSitesByPC(pc), state) } } - val scis = defSites.map { state.fpe2sci } - StringConstancyInformation.reduceMultiple(scis.flatten.toList) + StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) } else { StringConstancyInformation.lb } - state.appendToFpe2Sci(defSite, toAppend, reset = true) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), toAppend, reset = true) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala index 9a29ec61b4..9c4d1e3262 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala @@ -30,7 +30,8 @@ case class VirtualFunctionCallFinalizer( instr.name match { case "append" => finalizeAppend(instr, defSite) case "toString" => finalizeToString(instr, defSite) - case _ => state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) + case _ => + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb, reset = true) } } @@ -40,31 +41,33 @@ case class VirtualFunctionCallFinalizer( * [[org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1VirtualFunctionCallInterpreter]]. */ 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 receiverDefSitesByPC = + instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + receiverDefSitesByPC.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(receiverDefSitesByPC(pc), state) } } val receiverSci = StringConstancyInformation.reduceMultiple( - receiverDefSites.flatMap { s => + receiverDefSitesByPC.keys.toList.sorted.flatMap { pc => // 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) + val sci = state.fpe2sci(pc) + state.appendToFpe2Sci(pc, 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 paramDefSitesByPC = + instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + paramDefSitesByPC.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(paramDefSitesByPC(pc), state) } } - val appendSci = if (paramDefSites.forall(state.fpe2sci.contains)) { - StringConstancyInformation.reduceMultiple(paramDefSites.flatMap(state.fpe2sci(_))) + val appendSci = if (paramDefSitesByPC.keys.forall(state.fpe2sci.contains)) { + StringConstancyInformation.reduceMultiple(paramDefSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci(_))) } else StringConstancyInformation.lb val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { @@ -81,24 +84,24 @@ case class VirtualFunctionCallFinalizer( ) } - state.appendToFpe2Sci(defSite, finalSci, reset = true) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), 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 dependeeSites = instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + dependeeSites.keys.foreach { pc => + if (!state.fpe2sci.contains(pc)) { + state.iHandler.finalizeDefSite(dependeeSites(pc), state) } } val finalSci = StringConstancyInformation.reduceMultiple( - dependeeSites.toArray.flatMap { ds => state.fpe2sci(ds) } + dependeeSites.keys.toList.flatMap { pc => state.fpe2sci(pc) } ) // 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 { + dependeeSites.keys.foreach { state.appendToFpe2Sci(_, StringConstancyInformation.getNeutralElement, reset = true) } - state.appendToFpe2Sci(defSite, finalSci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala index 65d7ee5b76..1226a7d620 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -52,19 +52,20 @@ case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() - val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) - allDefSites.map { ds => (ds, exprHandler.processDefSite(ds)) }.foreach { - case (ds, ep) => + val allDefSitesByPC = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) + .map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap + allDefSitesByPC.keys.map { pc => (pc, exprHandler.processDefSite(allDefSitesByPC(pc))) }.foreach { + case (pc, ep) => if (ep.isFinal) - state.appendToFpe2Sci(ds, ep.asFinal.p.stringConstancyInformation) + state.appendToFpe2Sci(pc, ep.asFinal.p.stringConstancyInformation) results.append(ep) } // Add information of parameters - instr.arrayRef.asVar.definedBy.toArray.filter(_ < 0).foreach { ds => - val paramPos = Math.abs(ds + 2) + instr.arrayRef.asVar.toPersistentForm(state.tac.stmts).defPCs.filter(_ < 0).foreach { pc => + val paramPos = Math.abs(pc + 2) val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) - state.appendToFpe2Sci(ds, sci) + state.appendToFpe2Sci(pc, sci) } // If there is at least one InterimResult, return one. Otherwise, return a final result @@ -88,7 +89,7 @@ case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( )) } - state.appendToFpe2Sci(defSite, resultSci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), resultSci) results.head } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 6edc0888a9..e9a1f128f5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -146,7 +146,7 @@ case class L1FieldReadInterpreter[State <: ComputationState[State]]( StringConstancyLevel.DYNAMIC, possibleStrings = "(^null$|" + StringConstancyInformation.UnknownWordSymbol + ")" ) - state.appendToFpe2Sci(defSitEntity, StringConstancyInformation.lb) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) FinalEP(defSitEntity, StringConstancyProperty(sci)) } else { // If all results are final, determine all possible values for the field. Otherwise, @@ -164,7 +164,7 @@ case class L1FieldReadInterpreter[State <: ComputationState[State]]( val finalSci = StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.stringConstancyInformation }) - state.appendToFpe2Sci(defSitEntity, finalSci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) FinalEP(defSitEntity, StringConstancyProperty(finalSci)) } else { results.find(!_.isFinal).get diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 40ce1376f8..c80b2ae1f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -32,7 +32,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalize import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NonVirtualMethodCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.StaticFunctionCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionCallFinalizer -import org.opalj.value.ValueInformation /** * Responsible for processing expressions that are relevant in order to determine which value(s) a string read operation @@ -44,7 +43,7 @@ import org.opalj.value.ValueInformation * @author Patrick Mell */ class L1InterpretationHandler( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + tac: TAC, ps: PropertyStore, project: SomeProject, declaredFields: DeclaredFields, @@ -71,14 +70,17 @@ class L1InterpretationHandler( if (defSite < 0 && (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) ) { - state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) return FinalEP(e, StringConstancyProperty.lb) } else if (defSite < 0) { val sci = getParam(params, defSite) - state.appendToInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) return FinalEP(e, StringConstancyProperty(sci)) } else if (processedDefSites.contains(defSite)) { - state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) + state.appendToInterimFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + StringConstancyInformation.getNeutralElement + ) return FinalEP(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down @@ -104,7 +106,10 @@ class L1InterpretationHandler( case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) case _ => - state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) + state.appendToInterimFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + StringConstancyInformation.getNeutralElement + ) FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -125,8 +130,8 @@ class L1InterpretationHandler( case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } val sci = finalEP.p.stringConstancyInformation - state.appendToFpe2Sci(defSite, sci) - state.appendToInterimFpe2Sci(defSite, sci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) processedDefSites.remove(defSite) finalEP } @@ -151,7 +156,7 @@ class L1InterpretationHandler( processedDefSites.remove(defSite) StringConstancyInformation.lb } - state.appendToInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) r } @@ -175,7 +180,7 @@ class L1InterpretationHandler( processedDefSites.remove(defSite) StringConstancyInformation.lb } - state.appendToInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) r } @@ -184,8 +189,8 @@ class L1InterpretationHandler( */ private def processNew(expr: New, defSite: Int): FinalEP[Entity, StringConstancyProperty] = { val finalEP = NewInterpreter(cfg, this).interpret(expr) - state.appendToFpe2Sci(defSite, finalEP.p.stringConstancyInformation) - state.appendToInterimFpe2Sci(defSite, finalEP.p.stringConstancyInformation) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) finalEP } @@ -266,8 +271,8 @@ class L1InterpretationHandler( // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions val result = BinaryExprInterpreter(cfg, this).interpret(expr) val sci = result.p.stringConstancyInformation - state.appendToInterimFpe2Sci(defSite, sci) - state.appendToFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) result } @@ -333,10 +338,10 @@ class L1InterpretationHandler( val r = L1NonVirtualMethodCallInterpreter(cfg, this, state).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => - state.appendToInterimFpe2Sci(defSite, p.stringConstancyInformation) - state.appendToFpe2Sci(defSite, p.stringConstancyInformation) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) case _ => - state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) processedDefSites.remove(defSite) } r @@ -353,7 +358,7 @@ class L1InterpretationHandler( } else { StringConstancyInformation.lb } - state.appendToInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) } /** @@ -375,7 +380,11 @@ class L1InterpretationHandler( */ def finalizeDefSite(defSite: Int, state: L1ComputationState): Unit = { if (defSite < 0) { - state.appendToFpe2Sci(defSite, getParam(state.params.toSeq.map(_.toSeq), defSite), reset = true) + state.appendToFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + getParam(state.params.toSeq.map(_.toSeq), defSite), + reset = true + ) } else { stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] => @@ -397,7 +406,11 @@ class L1InterpretationHandler( case ExprStmt(_, sfc: StaticFunctionCall[V]) => StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case _ => - state.appendToFpe2Sci(defSite, StringConstancyInformation.lb, reset = true) + state.appendToFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + StringConstancyInformation.lb, + reset = true + ) } } } @@ -406,7 +419,7 @@ class L1InterpretationHandler( object L1InterpretationHandler { def apply( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + tac: TAC, ps: PropertyStore, project: SomeProject, declaredFields: DeclaredFields, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 5ea642bc1c..4e21f4c045 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -56,7 +56,7 @@ class L1NewArrayInterpreter[State <: ComputationState[State]]( 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) + state.appendToFpe2Sci(pcOfDefSite(d)(state.tac.stmts), r.asFinal.p.stringConstancyInformation) } r } @@ -67,7 +67,7 @@ class L1NewArrayInterpreter[State <: ComputationState[State]]( val paramPos = Math.abs(ds + 2) // lb is the fallback value val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) - state.appendToFpe2Sci(ds, sci) + state.appendToFpe2Sci(pcOfDefSite(ds)(state.tac.stmts), sci) val e: Integer = ds allResults ::= FinalEP(e, StringConstancyProperty(sci)) } @@ -88,7 +88,7 @@ class L1NewArrayInterpreter[State <: ComputationState[State]]( val toAppend = FinalEP(instr, StringConstancyProperty(resultSci)) allResults = toAppend :: allResults } - state.appendToFpe2Sci(defSite, resultSci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), resultSci) FinalEP(Integer.valueOf(defSite), StringConstancyProperty(resultSci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 552222dba7..8f863ec8a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -62,7 +62,7 @@ case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( case 0 => FinalEP(defSite, StringConstancyProperty.getNeutralElement) case _ => val results = init.params.head.asVar.definedBy.map { ds: Int => - (ds, exprHandler.processDefSite(ds, List())) + (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds, List())) } if (results.forall(_._2.isFinal)) { val reduced = StringConstancyInformation.reduceMultiple(results.map { r => @@ -74,9 +74,9 @@ case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( // intermediate result val returnIR = results.find(r => !r._2.isFinal).get._2 results.foreach { - case (ds, r) => + case (pc, r) => if (r.isFinal) { - state.appendToFpe2Sci(ds, r.asFinal.p.stringConstancyInformation, reset = true) + state.appendToFpe2Sci(pc, r.asFinal.p.stringConstancyInformation, reset = true) } case _ => } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index f93e383d85..5d9908abca 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -92,7 +92,7 @@ class L1StaticFunctionCallInterpreter( // 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, StringConstancyInformation.lb) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) return FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index c9d656e938..d9d89d1b67 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -83,7 +83,7 @@ class L1VirtualFunctionCallInterpreter( } if (result.isDefined) { - state.appendToFpe2Sci(defSite, result.get) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.get) } FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(result.getOrElse(StringConstancyInformation.lb))) } @@ -153,7 +153,10 @@ class L1VirtualFunctionCallInterpreter( L1StringAnalysis.registerParams(entity, evaluatedParams) ps(entity, StringConstancyProperty.key) match { case r: FinalEP[SContext, StringConstancyProperty] => - state.appendToFpe2Sci(defSite, r.p.stringConstancyInformation) + state.appendToFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + r.p.stringConstancyInformation + ) r case eps => state.dependees = eps :: state.dependees @@ -246,7 +249,7 @@ class L1VirtualFunctionCallInterpreter( )(implicit state: L1ComputationState): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted - val allResults = defSites.map(ds => (ds, exprHandler.processDefSite(ds, params))) + val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds, params))) val finalResults = allResults.filter(_._2.isFinal) val finalResultsWithoutNeutralElements = finalResults.filter { case (_, FinalEP(_, p: StringConstancyProperty)) => @@ -337,7 +340,7 @@ class L1VirtualFunctionCallInterpreter( } val e: Integer = defSites.head - state.appendToFpe2Sci(e, newValueSci, reset = true) + state.appendToFpe2Sci(pcOfDefSite(e)(state.tac.stmts), newValueSci, reset = true) FinalEP(e, StringConstancyProperty(finalSci)) } 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 79fe7a98c6..9b935dc785 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 @@ -19,11 +19,12 @@ import org.opalj.br.cfg.CFGNode * [[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 + * @author Maximilian Rüsch */ -abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { +abstract class AbstractPathFinder(tac: TAC) { + + val cfg: CFG[Stmt[V], TACStmts[V]] = tac.cfg + implicit val stmts: Array[Stmt[V]] = tac.stmts /** * CSInfo stores information regarding control structures (CS) in the form: Index of the start @@ -487,7 +488,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case (startSubpath, endSubpath) => val subpathElements = ListBuffer[SubPath]() if (fill) { - subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) + subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement.apply)) } if (!fill || subpathElements.nonEmpty) subPaths.append(NestedPathElement(subpathElements, None)) @@ -538,7 +539,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val subPaths: ListBuffer[SubPath] = startEndPairs.map { pair => val subpathElements = ListBuffer[SubPath]() if (fill) { - subpathElements.appendAll(Range.inclusive(pair._1, pair._2).map(FlatPathElement)) + subpathElements.appendAll(Range.inclusive(pair._1, pair._2).map(FlatPathElement.apply)) } NestedPathElement(subpathElements, None) } @@ -656,14 +657,14 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val subpathElements = ListBuffer[SubPath]() subPaths.append(NestedPathElement(subpathElements, None)) if (fill) { - subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) + subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement.apply)) } } // 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) }) + subPaths.appendAll((startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map(FlatPathElement.apply)) } ( @@ -1238,8 +1239,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } lastInsertedIndex = nextEle match { - case fpe: FlatPathElement => fpe.element - case inner: NestedPathElement => Path.getLastElementInNPE(inner).element + case fpe: FlatPathElement => fpe.stmtIndex(tac.pcToIndex) + case inner: NestedPathElement => Path.getLastElementInNPE(inner).stmtIndex(tac.pcToIndex) // Compiler wants it but should never be the case! case _ => -1 } @@ -1252,7 +1253,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (insertIndex < startEndPairs.length) { currentToInsert.appendAll((lastInsertedIndex + 1).to( startEndPairs(insertIndex)._2 - ).map(FlatPathElement)) + ).map(FlatPathElement.apply)) if (isRepElement) { npe.element.appendAll(currentToInsert) } else { @@ -1263,7 +1264,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { insertIndex.until(startEndPairs.length).foreach { i => insertPos = npe.element(i).asInstanceOf[NestedPathElement] insertPos.element.appendAll( - startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement) + startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement.apply) ) } } @@ -1282,7 +1283,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { indexLastCSEnd = nextTopCsInfo._2 + 1 } - finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement)) + finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement.apply)) Path(finalPath.toList) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index 5a4706e97c..e18beffdb9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -6,24 +6,20 @@ package analyses package string_analysis package preprocessing -import org.opalj.br.cfg.CFG - /** * 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 CFG on which this instance will operate on. - * - * @author Patrick Mell + * @author Maximilian Rüsch * * @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(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { +class DefaultPathFinder(tac: TAC) extends AbstractPathFinder(tac) { /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` @@ -42,7 +38,7 @@ class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFind val csInfo = findControlStructures(List(startSite), endSite) if (csInfo.isEmpty) { // In case the are no control structures, return a path from the first to the last element - Path(cfg.startBlock.startPC.until(endSite).map(FlatPathElement).toList) + Path(cfg.startBlock.startPC.until(cfg.code.instructions(endSite).pc).map(FlatPathElement.fromPC).toList) } else { // Otherwise, order the control structures and assign the corresponding path elements val orderedCS = hierarchicallyOrderControlStructures(csInfo) 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 48d9b79113..f2ee742003 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 @@ -24,10 +24,22 @@ import org.opalj.value.ValueInformation sealed class SubPath() /** - * A flat element, e.g., for representing a single statement. The statement is identified by - * `element`. + * A flat element, e.g., for representing a single statement. The statement is identified with `pc` by its [[org.opalj.br.PC]]. */ -case class FlatPathElement(element: Int) extends SubPath +class FlatPathElement private[FlatPathElement] (val pc: Int) extends SubPath { + def stmtIndex(implicit pcToIndex: Array[Int]): Int = valueOriginOfPC(pc, pcToIndex).get + + def copy = new FlatPathElement(pc) +} + +object FlatPathElement extends SubPath { + def apply(defSite: Int)(implicit stmts: Array[Stmt[V]]) = new FlatPathElement(pcOfDefSite(defSite)) + + def unapply(fpe: FlatPathElement)(implicit pcToIndex: Array[Int]): Some[Int] = Some(fpe.stmtIndex) + + def fromPC(pc: Int) = new FlatPathElement(pc) + def invalid = new FlatPathElement(-1) +} /** * Identifies the nature of a nested path element. @@ -89,22 +101,19 @@ 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 + * 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] = { + private def getAllDefAndUseSites(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): List[Int] = { val defAndUses = ListBuffer[Int]() val stack = mutable.Stack[Int](obj.definedBy.toList: _*) while (stack.nonEmpty) { - val popped = stack.pop() - if (!defAndUses.contains(popped)) { - defAndUses.append(popped) + val nextDefUseSite = stack.pop() + if (!defAndUses.contains(nextDefUseSite)) { + defAndUses.append(nextDefUseSite) - stmts(popped) match { + stmts(nextDefUseSite) match { case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] => val receiver = a.expr.asVirtualFunctionCall.receiver.asVar stack.pushAll(receiver.asVar.definedBy.filter(_ >= 0).toArray) @@ -121,17 +130,15 @@ case class Path(elements: List[SubPath]) { } /** - * 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. + * Checks whether a [[FlatPathElement]] with the given `element` is contained in the given `subpath`. 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 = { + private def containsPathElementWithPC(subpath: NestedPathElement, pc: 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 + case fpe: FlatPathElement => fpe.pc == pc + case npe: NestedPathElement => containsPathElementWithPC(npe, pc) + case e => throw new IllegalStateException(s"Unexpected path element $e") }) } } @@ -162,7 +169,7 @@ case class Path(elements: List[SubPath]) { npe.element.foreach { case innerNpe: NestedPathElement => if (innerNpe.elementType.isEmpty) { - if (!containsPathElement(innerNpe, endSite)) { + if (!containsPathElementWithPC(innerNpe, endSite)) { innerNpe.element.clear() } } else { @@ -179,30 +186,24 @@ case class Path(elements: List[SubPath]) { * [[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 relevantPCsMap Serves as a constant time look-up table to include only pcs that are of interest, in this + * case: That belong to some object. + * * @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], - endSite: Int + toProcess: NestedPathElement, + relevantPCsMap: Map[Int, Unit] ): Option[NestedPathElement] = { val elements = ListBuffer[SubPath]() toProcess.element.foreach { case fpe: FlatPathElement => - if (siteMap.contains(fpe.element)) { - elements.append(fpe.copy()) + if (relevantPCsMap.contains(fpe.pc)) { + elements.append(fpe.copy) } case npe: NestedPathElement => - val leanedSubPath = makeLeanPathAcc(npe, siteMap, endSite) + val leanedSubPath = makeLeanPathAcc(npe, relevantPCsMap) val keepAlternativeBranches = toProcess.elementType match { case Some(NestedPathType.CondWithAlternative) | Some(NestedPathType.SwitchWithDefault) | @@ -214,7 +215,7 @@ case class Path(elements: List[SubPath]) { } else if (keepAlternativeBranches) { elements.append(NestedPathElement(ListBuffer[SubPath](), None)) } - case _ => + case e => throw new IllegalStateException(s"Unexpected sub path element found: $e") } if (elements.nonEmpty) { @@ -225,28 +226,30 @@ case class Path(elements: List[SubPath]) { } /** - * Takes `this` path and transforms it into a new [[Path]] where only those sites are contained - * that either use or define `obj`. + * 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 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`. * - * @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. + * @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]]): Path = { + def makeLeanPath(obj: DUVar[ValueInformation])(implicit tac: TAC): Path = { + implicit val stmts: Array[Stmt[V]] = tac.stmts + implicit val pcToIndex: Array[Int] = tac.pcToIndex + 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 { + // Transform the list of relevant pcs into a map to have a constant access time + val defUseSites = getAllDefAndUseSites(obj, stmts) + val pcMap = defUseSites.filter { dus => + stmts(dus) match { case Assignment(_, _, expr: VirtualFunctionCall[V]) => val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) newOfObj == news || news.exists(newOfObj.contains) @@ -255,15 +258,15 @@ case class Path(elements: List[SubPath]) { newOfObj == news || news.exists(newOfObj.contains) case _ => true } - }.map { s => (s, ()) }.toMap + }.map { s => (pcOfDefSite(s), ()) }.toMap var leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.toArray.max elements.foreach { - case fpe: FlatPathElement if siteMap.contains(fpe.element) && fpe.element <= endSite => + case fpe: FlatPathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => leanPath.append(fpe) case npe: NestedPathElement => - val leanedPath = makeLeanPathAcc(npe, siteMap, endSite) + val leanedPath = makeLeanPathAcc(npe, pcMap) if (leanedPath.isDefined) { leanPath.append(leanedPath.get) } @@ -273,7 +276,7 @@ case class Path(elements: List[SubPath]) { // 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) TODO check loops again what is with loops that are never executed? - if (leanPath.tail.isEmpty) { + if (leanPath.tail.isEmpty) { // TODO this throws if lean path is only one element long leanPath.head match { case npe: NestedPathElement if npe.elementType.get == NestedPathType.Repetition || @@ -287,7 +290,7 @@ case class Path(elements: List[SubPath]) { leanPath.last match { case npe: NestedPathElement if npe.elementType.isDefined && - (npe.elementType.get != NestedPathType.TryCatchFinally || npe.elementType.get != NestedPathType.SwitchWithDefault) => + (npe.elementType.get != NestedPathType.TryCatchFinally && npe.elementType.get != NestedPathType.SwitchWithDefault) => val newLast = stripUnnecessaryBranches(npe, endSite) leanPath.remove(leanPath.size - 1) leanPath.append(newLast) @@ -302,7 +305,8 @@ case class Path(elements: List[SubPath]) { object Path { /** - * Returns the very last [[FlatPathElement]] in this path, respecting any nesting structure. + * Returns the very last [[FlatPathElement]] in this path, respecting any nesting structure. If no last element + * exists, [[FlatPathElement.invalid]] is returned. */ @tailrec def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { npe.element.last match { @@ -311,9 +315,9 @@ object Path { npe.element.last match { case fpe: FlatPathElement => fpe case innerNpe: NestedPathElement => getLastElementInNPE(innerNpe) - case _ => FlatPathElement(-1) + case _ => FlatPathElement.invalid } - case _ => FlatPathElement(-1) + case _ => FlatPathElement.invalid } } } 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 9ff969682d..f66ab0790c 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 @@ -43,16 +43,16 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle )(implicit state: State): Option[StringTree] = { subpath match { case fpe: FlatPathElement => - val sci = if (fpe2Sci.contains(fpe.element)) { - StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) + val sci = if (fpe2Sci.contains(fpe.pc)) { + StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.pc)) } else { - val sciToAdd = interpretationHandler.processDefSite(fpe.element) match { + val sciToAdd = interpretationHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { case FinalP(p) => p.stringConstancyInformation case InterimUBP(ub) => ub.stringConstancyInformation case _ => StringConstancyInformation.lb } - fpe2Sci(fpe.element) = ListBuffer(sciToAdd) + fpe2Sci(fpe.pc) = ListBuffer(sciToAdd) sciToAdd } Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) @@ -117,7 +117,7 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle * 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 + * @param fpe2Sci A mapping from [[FlatPathElement.pc]] 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 diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index 7d2fb5b471..8ace58d206 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -6,23 +6,19 @@ package analyses package string_analysis package preprocessing -import org.opalj.br.cfg.CFG - /** * 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 + * @author Maximilian Rüsch * * @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) { +case class WindowPathFinder(tac: TAC) extends AbstractPathFinder(tac) { /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` @@ -62,10 +58,8 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde } 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) + Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(FlatPathElement.fromPC).toList) } else { // Otherwise, order the control structures and assign the corresponding path elements val orderedCS = hierarchicallyOrderControlStructures(csInfo) 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 1be9d747e9..0596bcb097 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 @@ -17,6 +17,8 @@ import org.opalj.fpcf.EOptionP */ package object string_analysis { + type TAC = TACode[TACMethodParameter, V] + /** * The type of entities the string analysis process. * @@ -46,5 +48,4 @@ package object string_analysis { * calls. */ type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[SContext, (Int, Int, Int)]] - } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala index 846a97c8ea..74450ac068 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala @@ -46,19 +46,21 @@ package object tac { ) } + final def valueOriginOfPC(pc: Int, pcToIndex: Array[Int]): Option[ValueOrigin] = { + if (ai.underlyingPC(pc) < 0) + Some(pc) // parameter + else if (pc >= 0 && pcToIndex(pc) >= 0) + Some(pcToIndex(pc)) // local + else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) + Some(ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc)))) + else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) + Some(ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc)))) + else + None + } + final def valueOriginsOfPCs(pcs: PCs, pcToIndex: Array[Int]): IntTrieSet = { - pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => - if (ai.underlyingPC(pc) < 0) - origins + pc // parameter - else if (pc >= 0 && pcToIndex(pc) >= 0) - origins + pcToIndex(pc) // local - else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) - origins + ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc))) - else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) - origins + ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc))) - else - origins // as is - } + pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => origins ++ valueOriginOfPC(pc, pcToIndex) } } /** From 6bbcfd2b6671fc11e0a19b97ca90e9968630f91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 9 Feb 2024 15:22:22 +0100 Subject: [PATCH 354/583] Start support of string_concat calls on l0 level --- .../string_analysis/StringAnalysis.scala | 10 +- .../InterpretationHandler.scala | 22 ++- .../interpretation/StringInterpreter.scala | 66 +++------ .../string_analysis/l0/L0StringAnalysis.scala | 40 ++++-- .../L0InterpretationHandler.scala | 39 ++++-- .../L0StaticFunctionCallInterpreter.scala | 82 ++++++++++- .../string_analysis/l1/L1StringAnalysis.scala | 130 ++---------------- .../L1ArrayAccessInterpreter.scala | 5 +- .../L1FieldReadInterpreter.scala | 2 +- .../L1InterpretationHandler.scala | 72 ++++------ .../L1NewArrayInterpreter.scala | 7 +- .../L1NonVirtualMethodCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 19 +-- .../interpretation/L1StringInterpreter.scala | 30 ++++ .../L1VirtualFunctionCallInterpreter.scala | 23 ++-- 15 files changed, 263 insertions(+), 286 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 2314371231..42322d84d1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -11,7 +11,6 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.FieldType import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey -import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -41,8 +40,6 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringAnalysis extends FPCFAnalysis { - override val project: SomeProject - type State <: ComputationState[State] val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) @@ -77,7 +74,7 @@ trait StringAnalysis extends FPCFAnalysis { * the possible string values. This method returns either a final [[Result]] or an * [[InterimResult]] depending on whether other information needs to be computed first. */ - protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult + protected[string_analysis] def determinePossibleStrings(implicit state: State): ProperPropertyComputationResult /** * Continuation function for this analysis. @@ -233,10 +230,7 @@ trait StringAnalysis extends FPCFAnalysis { p.elements.foreach { case fpe: FlatPathElement => if (!state.fpe2sci.contains(fpe.pc)) { - val eOptP = state.iHandler.processDefSite( - valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get, - state.params.toList.map(_.toSeq) - ) + val eOptP = state.iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) if (eOptP.isFinal) { state.appendToFpe2Sci(fpe.pc, eOptP.asFinal.p.stringConstancyInformation, reset = true) } else { 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 a2181e64b7..1c8642fd66 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 @@ -17,7 +17,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) { @@ -51,10 +50,7 @@ abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). * The entity of the result will be the given `defSite`. */ - def processDefSite( - defSite: Int, - params: List[Seq[StringConstancyInformation]] = List() - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] + def processDefSite(defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As @@ -72,6 +68,20 @@ abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) * Finalized a given definition state. */ def finalizeDefSite(defSite: Int, state: State): Unit + + /** + * 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. + */ + protected def getParam(params: Seq[Seq[StringConstancyInformation]], defSite: Int): StringConstancyInformation = { + val paramPos = Math.abs(defSite + 2) + if (params.exists(_.length <= paramPos)) { + // IMPROVE cant we just map each list of params with a nonexistent pos to lb and still reduce? + StringConstancyInformation.lb + } else { + StringConstancyInformation.reduceMultiple(params.map(_(paramPos)).distinct) + } + } } object InterpretationHandler { @@ -106,7 +116,7 @@ object InterpretationHandler { */ def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = expr.asVar.value.isPrimitiveValue && - L1StringAnalysis.isSupportedPrimitiveNumberType( + StringAnalysis.isSupportedPrimitiveNumberType( expr.asVar.value.asPrimitiveValue.primitiveType.toJava ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index a0a22eca27..48f989dd81 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -9,13 +9,9 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.NoContext 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.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -73,39 +69,12 @@ trait StringInterpreter[State <: ComputationState[State]] { } /** - * 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). + * Extracts all parameters of the function calls at the given `pcs`. */ - protected def getMethodsForPC(pc: Int)( - implicit - ps: PropertyStore, - callees: Callees, - contextProvider: ContextProvider - ): (List[Method], Boolean) = { - var hasMethodWithUnknownBody = false - val methods = ListBuffer[Method]() - - callees.callees(NoContext, pc)(ps, contextProvider).map(_.method).foreach { - case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) - case _ => hasMethodWithUnknownBody = true - } - - (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: TAC - ): List[Seq[Expr[V]]] = { + protected def getParametersForPCs(pcs: Iterable[Int])(implicit state: State): List[Seq[Expr[V]]] = { val paramLists = ListBuffer[Seq[Expr[V]]]() - pcs.map(tac.pcToIndex).foreach { stmtIndex => - val params = tac.stmts(stmtIndex) match { + pcs.map(state.tac.pcToIndex).foreach { stmtIndex => + val params = state.tac.stmts(stmtIndex) match { case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params case Assignment(_, _, fc: FunctionCall[V]) => fc.params case _ => Seq() @@ -130,11 +99,9 @@ trait StringInterpreter[State <: ComputationState[State]] { * entities to functions, `entity2function`. */ protected def evaluateParameters( - params: List[Seq[Expr[V]]], - iHandler: InterpretationHandler[State], - funCall: FunctionCall[V], - functionArgsPos: NonFinalFunctionArgsPos, - entity2function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] + params: List[Seq[Expr[V]]], + iHandler: InterpretationHandler[State], + funCall: FunctionCall[V] )(implicit state: State): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { @@ -143,15 +110,15 @@ trait StringInterpreter[State <: ComputationState[State]] { case (ds, innerIndex) => val ep = iHandler.processDefSite(ds) if (ep.isRefinable) { - if (!functionArgsPos.contains(funCall)) { - functionArgsPos(funCall) = mutable.Map() + if (!state.nonFinalFunctionArgsPos.contains(funCall)) { + state.nonFinalFunctionArgsPos(funCall) = mutable.Map() } val e = ep.e.asInstanceOf[SContext] - functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) - if (!entity2function.contains(e)) { - entity2function(e) = ListBuffer() + state.nonFinalFunctionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) + if (!state.entity2Function.contains(e)) { + state.entity2Function(e) = ListBuffer() } - entity2function(e).append(funCall) + state.entity2Function(e).append(funCall) } ep }) @@ -159,11 +126,10 @@ trait StringInterpreter[State <: ComputationState[State]] { }) /** - * This function checks whether the interpretation of parameters, as, e.g., produced by - * [[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. + * Checks whether the interpretation of parameters, as, e.g., produced by [[evaluateParameters()]], is final or not + * and returns all refinable results as a list. Hence, an empty list is returned, all parameters are fully evaluated. */ - protected def getNonFinalParameters( + protected def getRefinableParameterResults( 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/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 5a5337c18f..da0ae4539f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -86,26 +86,49 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis determinePossibleStrings(state) } - override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { - implicit val _state: State = state - + override protected[string_analysis] def determinePossibleStrings(implicit + state: State + ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac val stmts = tac.stmts val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(tac.pcToIndex) 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 handled further down + + if (state.params.isEmpty) { + state.params = StringAnalysis.getParams(state.entity) + } + + if (state.params.isEmpty && defSites.exists(_ < 0)) { + if (InterpretationHandler.isStringConstExpression(uVar)) { + // We can evaluate string const expressions as function parameters + } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { + val numType = uVar.value.asPrimitiveValue.primitiveType.toJava + val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) + return Result(state.entity, StringConstancyProperty(sci)) + } else { + // StringBuilders as parameters are currently not evaluated + return Result(state.entity, StringConstancyProperty.lb) + } + } + + if (state.parameterDependeesCount > 0) { + return getInterimResult(state) + } else { + state.isSetupCompleted = true + } + + // 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) + return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) } val expr = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { val leanPath = computeLeanPathForStringBuilder(uVar) if (leanPath.isEmpty) { - // String{Builder,Buffer} from method parameter is to be evaluated return Result(state.entity, StringConstancyProperty.lb) } state.computedLeanPath = leanPath.get @@ -135,10 +158,9 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } } else { // We deal with pure strings TODO unify result handling - val interpretationHandler = L0InterpretationHandler(tac) val sci = StringConstancyInformation.reduceMultiple( uVar.definedBy.toArray.sorted.map { ds => - interpretationHandler.processDefSite(ds).asFinal.p.stringConstancyInformation + state.iHandler.processDefSite(ds).asFinal.p.stringConstancyInformation } ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index a7caea5a97..c2e9cc06ba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -7,11 +7,14 @@ package string_analysis package l0 package interpretation +import org.opalj.ai.ImmediateVMExceptionsOriginOffset +import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter @@ -32,6 +35,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt */ class L0InterpretationHandler( tac: TAC +)( + implicit + p: SomeProject, + ps: PropertyStore ) extends InterpretationHandler[L0ComputationState](tac) { /** @@ -39,19 +46,29 @@ class L0InterpretationHandler( *

    * @inheritdoc */ - override def processDefSite( - defSite: Int, - params: List[Seq[StringConstancyInformation]] = List() - )(implicit state: L0ComputationState): EOptionP[Entity, StringConstancyProperty] = { + override def processDefSite(defSite: Int)(implicit + state: L0ComputationState + ): 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 - // Function parameters are not evaluated but regarded as unknown + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + if (defSite < 0) { - return FinalEP(e, StringConstancyProperty.lb) + val params = state.params.toList.map(_.toList) + if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { + state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.lb) + return FinalEP(e, StringConstancyProperty.lb) + } else { + val sci = getParam(params, defSite) + state.appendToInterimFpe2Sci(defSitePC, sci) + return FinalEP(e, StringConstancyProperty(sci)) + } } else if (processedDefSites.contains(defSite)) { + state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) return FinalEP(e, StringConstancyProperty.getNeutralElement) } + processedDefSites(defSite) = () stmts(defSite) match { @@ -86,7 +103,9 @@ class L0InterpretationHandler( L0VirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc, defSite) - case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) + case _ => + state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) + FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -95,5 +114,9 @@ class L0InterpretationHandler( object L0InterpretationHandler { - def apply(tac: TAC): L0InterpretationHandler = new L0InterpretationHandler(tac) + def apply(tac: TAC)( + implicit + p: SomeProject, + ps: PropertyStore + ): L0InterpretationHandler = new L0InterpretationHandler(tac) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index fd83497ecd..e30cd80d78 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -7,28 +7,98 @@ package string_analysis package l0 package interpretation +import org.opalj.br.analyses.SomeProject 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.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * The `IntraproceduralStaticFunctionCallInterpreter` is responsible for processing - * [[StaticFunctionCall]]s in an intraprocedural fashion. - *

    - * For supported method calls, see the documentation of the `interpret` function. + * Processes [[StaticFunctionCall]]s in without a call graph. * * @author Maximilian Rüsch */ case class L0StaticFunctionCallInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], override protected val exprHandler: InterpretationHandler[State] +)( + implicit + p: SomeProject, + ps: PropertyStore ) extends L0StringInterpreter[State] { override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) + if (calleeMethod.isEmpty) { + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) + return FinalEP(instr, StringConstancyProperty.lb) + } + + // 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 params = if (state.nonFinalFunctionArgs.contains(instr)) { + state.nonFinalFunctionArgs(instr) + } else { + evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) + } + + val m = calleeMethod.value + val (_, calleeTac) = getTACAI(ps, m, state) + + // Continue only when all parameter information are available + val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + if (refinableResults.nonEmpty) { + // question why do we depend on the return value of the call + if (calleeTac.isDefined) { + val returns = calleeTac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + returns.foreach { ret => + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), 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) + return refinableResults.head + } + + state.nonFinalFunctionArgs.remove(instr) + state.nonFinalFunctionArgsPos.remove(instr) + val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) + if (calleeTac.isDefined) { + state.removeFromMethodPrep2defSite(m, defSite) + // TAC available => Get return UVar and start the string analysis + val returns = calleeTac.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 + FinalEP(instr, StringConstancyProperty.lb) + } else { + val results = returns.map { ret => + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m) + StringAnalysis.registerParams(entity, evaluatedParams) + + val eps = ps(entity, StringConstancyProperty.key) + if (eps.isRefinable) { + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) + } + eps + } + results.find(_.isRefinable).getOrElse(results.head) + } + } else { + // No TAC => Register dependee and continue + state.appendToMethodPrep2defSite(m, defSite) + EPK(state.entity, StringConstancyProperty.key) + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index fe093a3ccc..bf00ceeed6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -6,7 +6,6 @@ package analyses package string_analysis package l1 -import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.FieldType @@ -26,8 +25,6 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.ProperPropertyComputationResult @@ -138,7 +135,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * the possible string values. This method returns either a final [[Result]] or an * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. */ - override protected[string_analysis] def determinePossibleStrings(state: State): ProperPropertyComputationResult = { + override protected[string_analysis] def determinePossibleStrings(implicit + state: State + ): ProperPropertyComputationResult = { val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted @@ -182,7 +181,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { var requiresCallersInfo = false if (state.params.isEmpty) { - state.params = L1StringAnalysis.getParams(state.entity) + state.params = StringAnalysis.getParams(state.entity) } if (state.params.isEmpty) { // In case a parameter is required for approximating a string, retrieve callers information @@ -191,9 +190,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { requiresCallersInfo = if (defSites.exists(_ < 0)) { if (InterpretationHandler.isStringConstExpression(uVar)) { hasCallersOrParamInfo - } else if (L1StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { + } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { val numType = uVar.value.asPrimitiveValue.primitiveType.toJava - val sci = L1StringAnalysis.getDynamicStringInformationForNumberType(numType) + val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) return Result(state.entity, StringConstancyProperty(sci)) } else { // StringBuilders as parameters are currently not evaluated @@ -207,7 +206,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { return Result(state.entity, StringConstancyProperty.lb) } val hasSupportedParamType = state.entity._2.parameterTypes.exists { - L1StringAnalysis.isSupportedType + StringAnalysis.isSupportedType } if (hasSupportedParamType) { hasFormalParamUsageAlongPath(state.computedLeanPath)(state.tac) @@ -242,7 +241,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { // 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.map(_.toList))(state) + val r = state.iHandler.processDefSite(defSites.head)(state) return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) } @@ -302,7 +301,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.dependees.nonEmpty) { getInterimResult(state) } else { - L1StringAnalysis.unregisterParams(state.entity) + StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(sci)) } } @@ -357,11 +356,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } /** - * 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 [[L1StringAnalysis.registerParams]]. - * The return value of this function indicates whether a the parameter evaluation is done - * (`true`) or not yet (`false`). + * This method takes a computation `state`, and determines the interpretations of all parameters of the method under + * analysis. These interpretations are registered using [[StringAnalysis.registerParams]]. The return value of this + * function indicates whether the parameter evaluation is done (`true`) or not yet (`false`). */ private def registerParams(state: L1ComputationState): Boolean = { val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq @@ -396,7 +393,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { ))) } // Recursively analyze supported types - if (L1StringAnalysis.isSupportedType(p.asVar)) { + if (StringAnalysis.isSupportedType(p.asVar)) { val paramEntity = (p.asVar.toPersistentForm(state.tac.stmts), m.definedMethod) val eps = propertyStore(paramEntity, StringConstancyProperty.key) state.appendToVar2IndexMapping(paramEntity._1, paramIndex) @@ -413,12 +410,11 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state.params(methodIndex)(paramIndex) = StringConstancyProperty.lb.stringConstancyInformation } - } } // If all parameters could already be determined, register them if (!hasIntermediateResult) { - L1StringAnalysis.registerParams(state.entity, state.params) + StringAnalysis.registerParams(state.entity, state.params) } !hasIntermediateResult } @@ -431,109 +427,13 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { object L1StringAnalysis { - final val FieldWriteThresholdConfigKey = { + private[l1] final val FieldWriteThresholdConfigKey = { "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.fieldWriteThreshold" } private final val CallersThresholdConfigKey = { "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.callersThreshold" } - - /** - * 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[ListBuffer[StringConstancyInformation]]]() - - def registerParams(e: Entity, scis: ListBuffer[ListBuffer[StringConstancyInformation]]): Unit = { - if (!paramInfos.contains(e)) { - paramInfos(e) = scis - } else { - paramInfos(e).appendAll(scis) - } - } - - def unregisterParams(e: Entity): Unit = paramInfos.remove(e) - - def getParams(e: Entity): ListBuffer[ListBuffer[StringConstancyInformation]] = - if (paramInfos.contains(e)) { - paramInfos(e) - } else { - 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 = - v.value.isPrimitiveValue && isSupportedPrimitiveNumberType(v.value.asPrimitiveValue.primitiveType.toJava) - - /** - * 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 - * `typeName` may be approximated by the string analysis better than just the lower bound. - * - * @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" || isSupportedPrimitiveNumberType(typeName) || - typeName == "java.lang.String" || typeName == "java.lang.String[]" - - /** - * Determines whether a given element 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 [[L1StringAnalysis.isSupportedType(String)]]. - */ - def isSupportedType(v: V): Boolean = - if (v.value.isPrimitiveValue) { - isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) - } else { - try { - 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 [[L1StringAnalysis.isSupportedType(String)]]. - */ - 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 L1StringAnalysisScheduler extends FPCFAnalysisScheduler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala index 1226a7d620..1095ca783b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -37,8 +37,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], override protected val exprHandler: InterpretationHandler[State], - state: State, - params: List[Seq[StringConstancyInformation]] + state: State ) extends L1StringInterpreter[State] { override type T = ArrayLoad[V] @@ -64,7 +63,7 @@ case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( // Add information of parameters instr.arrayRef.asVar.toPersistentForm(state.tac.stmts).defPCs.filter(_ < 0).foreach { pc => val paramPos = Math.abs(pc + 2) - val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) + val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) state.appendToFpe2Sci(pc, sci) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index e9a1f128f5..3406113627 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -91,7 +91,7 @@ case class L1FieldReadInterpreter[State <: ComputationState[State]]( // String analysis could then use the field analysis. val defSitEntity: Integer = defSite // Unknown type => Cannot further approximate - if (!L1StringAnalysis.isSupportedType(instr.declaredFieldType)) { + if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { return FinalEP(instr, StringConstancyProperty.lb) } // Write accesses exceeds the threshold => approximate with lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index c80b2ae1f6..67af32e638 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -58,24 +58,24 @@ class L1InterpretationHandler( * * @inheritdoc */ - override def processDefSite( - defSite: Int, - params: List[Seq[StringConstancyInformation]] = List() - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + override def processDefSite(defSite: Int)(implicit + state: L1ComputationState + ): 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 // Function parameters are not evaluated when none are present (this always includes the // implicit parameter for "this" and for exceptions thrown outside the current function) - if (defSite < 0 && - (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) - ) { - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalEP(e, StringConstancyProperty.lb) - } else if (defSite < 0) { - val sci = getParam(params, defSite) - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - return FinalEP(e, StringConstancyProperty(sci)) + if (defSite < 0) { + val params = state.params.toList.map(_.toList) + if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) + return FinalEP(e, StringConstancyProperty.lb) + } else { + val sci = getParam(params, defSite) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + return FinalEP(e, StringConstancyProperty(sci)) + } } else if (processedDefSites.contains(defSite)) { state.appendToInterimFpe2Sci( pcOfDefSite(defSite)(state.tac.stmts), @@ -91,15 +91,15 @@ class L1InterpretationHandler( case Assignment(_, _, expr: IntConst) => processConstExpr(expr, defSite) case Assignment(_, _, expr: FloatConst) => processConstExpr(expr, defSite) case Assignment(_, _, expr: DoubleConst) => processConstExpr(expr, defSite) // TODO what about long consts - case Assignment(_, _, expr: ArrayLoad[V]) => processArrayLoad(expr, defSite, params) - case Assignment(_, _, expr: NewArray[V]) => processNewArray(expr, defSite, params) + case Assignment(_, _, expr: ArrayLoad[V]) => processArrayLoad(expr, defSite) + case Assignment(_, _, expr: NewArray[V]) => processNewArray(expr, defSite) case Assignment(_, _, expr: New) => processNew(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]) => processStaticFunctionCall(expr, defSite, params) - case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite, params) + case Assignment(_, _, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite) + case ExprStmt(_, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite) + case Assignment(_, _, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite) + case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) @@ -141,14 +141,12 @@ class L1InterpretationHandler( */ private def processArrayLoad( expr: ArrayLoad[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { val r = new L1ArrayAccessInterpreter( cfg, this, - state, - params + state ).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation @@ -165,14 +163,12 @@ class L1InterpretationHandler( */ private def processNewArray( expr: NewArray[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new L1NewArrayInterpreter( cfg, this, - state, - params + state ).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation @@ -199,14 +195,12 @@ class L1InterpretationHandler( */ private def processVFC( expr: VirtualFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new L1VirtualFunctionCallInterpreter( cfg, this, ps, - params, contextProvider ).interpret(expr, defSite) // Set whether the virtual function call is fully prepared. This is the case if 1) the @@ -245,15 +239,13 @@ class L1InterpretationHandler( */ private def processStaticFunctionCall( expr: StaticFunctionCall[V], - defSite: Int, - params: List[Seq[StringConstancyInformation]] + defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new L1StaticFunctionCallInterpreter( cfg, this, ps, state, - params, contextProvider ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { @@ -361,20 +353,6 @@ class L1InterpretationHandler( state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) } - /** - * 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) - if (params.exists(_.length <= paramPos)) { - StringConstancyInformation.lb - } else { - val paramScis = params.map(_(paramPos)).distinct - StringConstancyInformation.reduceMultiple(paramScis) - } - } - /** * Finalized a given definition state. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 4e21f4c045..4199acdb95 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -28,8 +28,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation class L1NewArrayInterpreter[State <: ComputationState[State]]( override protected val cfg: CFG[Stmt[V], TACStmts[V]], override protected val exprHandler: InterpretationHandler[State], - state: State, - params: List[Seq[StringConstancyInformation]] + state: State ) extends L1StringInterpreter[State] { override type T = NewArray[V] @@ -54,7 +53,7 @@ class L1NewArrayInterpreter[State <: ComputationState[State]]( }.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) + val r = exprHandler.processDefSite(d) if (r.isFinal) { state.appendToFpe2Sci(pcOfDefSite(d)(state.tac.stmts), r.asFinal.p.stringConstancyInformation) } @@ -66,7 +65,7 @@ class L1NewArrayInterpreter[State <: ComputationState[State]]( arrValuesDefSites.filter(_ < 0).foreach { ds => val paramPos = Math.abs(ds + 2) // lb is the fallback value - val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) + val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) state.appendToFpe2Sci(pcOfDefSite(ds)(state.tac.stmts), sci) val e: Integer = ds allResults ::= FinalEP(e, StringConstancyProperty(sci)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 8f863ec8a5..7951f6469a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -62,7 +62,7 @@ case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( case 0 => FinalEP(defSite, StringConstancyProperty.getNeutralElement) case _ => val results = init.params.head.asVar.definedBy.map { ds: Int => - (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds, List())) + (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds)) } if (results.forall(_._2.isFinal)) { val reduced = StringConstancyInformation.reduceMultiple(results.map { r => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 5d9908abca..617631d6fc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -33,7 +33,6 @@ class L1StaticFunctionCallInterpreter( override protected val exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, implicit val state: L1ComputationState, - params: List[Seq[StringConstancyInformation]], contextProvider: ContextProvider ) extends L1StringInterpreter[L1ComputationState] { @@ -60,7 +59,7 @@ class L1StaticFunctionCallInterpreter( private def processStringValueOf(call: StaticFunctionCall[V])( implicit state: L1ComputationState ): EOptionP[Entity, StringConstancyProperty] = { - val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_, params) } + val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_) } val interim = results.find(_.isRefinable) if (interim.isDefined) { interim.get @@ -113,17 +112,11 @@ class L1StaticFunctionCallInterpreter( val params = if (state.nonFinalFunctionArgs.contains(instr)) { state.nonFinalFunctionArgs(instr) } else { - evaluateParameters( - getParametersForPCs(relevantPCs, state.tac), - exprHandler, - instr, - state.nonFinalFunctionArgsPos, - state.entity2Function - ) + evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) } // Continue only when all parameter information are available - val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (nonFinalResults.nonEmpty) { + val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + if (refinableResults.nonEmpty) { if (tac.isDefined) { val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) returns.foreach { ret => @@ -135,7 +128,7 @@ class L1StaticFunctionCallInterpreter( } state.nonFinalFunctionArgs(instr) = params state.appendToMethodPrep2defSite(m, defSite) - return nonFinalResults.head + return refinableResults.head } state.nonFinalFunctionArgs.remove(instr) @@ -152,7 +145,7 @@ class L1StaticFunctionCallInterpreter( } else { val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), m) - L1StringAnalysis.registerParams(entity, evaluatedParams) + StringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index a03138f63c..3700e432d6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -7,11 +7,19 @@ package string_analysis package l1 package interpretation +import scala.collection.mutable.ListBuffer + +import org.opalj.br.DefinedMethod +import org.opalj.br.Method +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.NoContext 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** @@ -37,6 +45,28 @@ trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterp */ def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] + /** + * 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 getMethodsForPC(pc: Int)( + implicit + ps: PropertyStore, + callees: Callees, + contextProvider: ContextProvider + ): (List[Method], Boolean) = { + var hasMethodWithUnknownBody = false + val methods = ListBuffer[Method]() + + callees.callees(NoContext, pc)(ps, contextProvider).map(_.method).foreach { + case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) + case _ => hasMethodWithUnknownBody = true + } + + (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) + } + protected def handleInterpretationResult(ep: EOptionP[Entity, StringConstancyProperty])(implicit state: State ): Option[StringConstancyInformation] = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index d9d89d1b67..52f01db56f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -35,7 +35,6 @@ class L1VirtualFunctionCallInterpreter( override protected val cfg: CFG[Stmt[V], TACStmts[V]], override protected val exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, - params: List[Seq[StringConstancyInformation]], contextProvider: ContextProvider ) extends L1StringInterpreter[L1ComputationState] { @@ -119,17 +118,11 @@ class L1VirtualFunctionCallInterpreter( val params = if (state.nonFinalFunctionArgs.contains(instr)) { state.nonFinalFunctionArgs(instr) } else { - evaluateParameters( - getParametersForPCs(relevantPCs, state.tac), - exprHandler, - instr, - state.nonFinalFunctionArgsPos, - state.entity2Function - ) + evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) } // Continue only when all parameter information are available - val nonFinalResults = getNonFinalParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (nonFinalResults.nonEmpty) { + val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + if (refinableResults.nonEmpty) { state.nonFinalFunctionArgs(instr) = params return None } @@ -150,7 +143,7 @@ class L1VirtualFunctionCallInterpreter( val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) - L1StringAnalysis.registerParams(entity, evaluatedParams) + StringAnalysis.registerParams(entity, evaluatedParams) ps(entity, StringConstancyProperty.key) match { case r: FinalEP[SContext, StringConstancyProperty] => state.appendToFpe2Sci( @@ -249,7 +242,7 @@ class L1VirtualFunctionCallInterpreter( )(implicit state: L1ComputationState): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted - val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds, params))) + val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) val finalResults = allResults.filter(_._2.isFinal) val finalResultsWithoutNeutralElements = finalResults.filter { case (_, FinalEP(_, p: StringConstancyProperty)) => @@ -280,7 +273,7 @@ class L1VirtualFunctionCallInterpreter( // .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)) + val values = defSites.map(exprHandler.processDefSite(_)) // Defer the computation if there is at least one intermediate result if (values.exists(_.isRefinable)) { @@ -298,7 +291,7 @@ class L1VirtualFunctionCallInterpreter( newValueSci = StringConstancyInformation.lb } else { val ds = cfg.code.instructions(headSite).asAssignment.targetVar.usedBy.toArray.min - val r = exprHandler.processDefSite(ds, params) + val r = exprHandler.processDefSite(ds) r match { case FinalP(p) => newValueSci = p.stringConstancyInformation // Defer the computation if there is no final result yet @@ -352,7 +345,7 @@ class L1VirtualFunctionCallInterpreter( private def interpretToStringCall(call: VirtualFunctionCall[V])( implicit state: L1ComputationState ): Option[StringConstancyInformation] = - handleInterpretationResult(exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params)) + handleInterpretationResult(exprHandler.processDefSite(call.receiver.asVar.definedBy.head)) /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. From 74acf8cbe0308aea6c5941ed838c58a7eb832be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 9 Feb 2024 23:12:42 +0100 Subject: [PATCH 355/583] Refactor state dependency of interpretation handlers --- .../BinaryExprInterpreter.scala | 6 +- .../DependingStringInterpreter.scala | 38 +++++++ .../DoubleValueInterpreter.scala | 6 +- .../FloatValueInterpreter.scala | 6 +- .../IntegerValueInterpreter.scala | 6 +- .../InterpretationHandler.scala | 31 ++---- .../interpretation/NewInterpreter.scala | 6 +- .../StringConstInterpreter.scala | 9 +- .../interpretation/StringInterpreter.scala | 16 --- .../string_analysis/l0/L0StringAnalysis.scala | 7 +- .../interpretation/L0ArrayInterpreter.scala | 11 +- .../L0GetFieldInterpreter.scala | 7 +- .../L0GetStaticInterpreter.scala | 7 +- .../L0InterpretationHandler.scala | 40 ++++--- .../L0NonVirtualMethodCallInterpreter.scala | 9 +- .../L0StaticFunctionCallInterpreter.scala | 4 +- .../interpretation/L0StringInterpreter.scala | 12 --- .../L0VirtualFunctionCallInterpreter.scala | 27 +++-- .../L0VirtualMethodCallInterpreter.scala | 7 +- .../string_analysis/l1/L1StringAnalysis.scala | 22 +--- .../L1ArrayAccessInterpreter.scala | 15 +-- .../L1FieldReadInterpreter.scala | 16 +-- .../L1InterpretationHandler.scala | 100 +++++++----------- .../L1NewArrayInterpreter.scala | 7 +- .../L1NonVirtualFunctionCallInterpreter.scala | 8 +- .../L1NonVirtualMethodCallInterpreter.scala | 5 +- .../L1StaticFunctionCallInterpreter.scala | 16 ++- .../interpretation/L1StringInterpreter.scala | 14 --- .../L1VirtualFunctionCallInterpreter.scala | 15 ++- .../L1VirtualMethodCallInterpreter.scala | 7 +- 30 files changed, 171 insertions(+), 309 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala 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 a2dcc70539..c4bc20ea0f 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 @@ -8,7 +8,6 @@ package interpretation import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt -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.fpcf.FinalEP @@ -19,10 +18,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class BinaryExprInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object BinaryExprInterpreter extends StringInterpreter[Nothing] { override type T = BinaryExpr[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala new file mode 100644 index 0000000000..98fc5ef4df --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala @@ -0,0 +1,38 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation + +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalP + +/** + * @author Maximilian Rüsch + */ +trait DependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { + + protected def handleDependentDefSite(defSite: Int)(implicit + state: State, + exprHandler: InterpretationHandler[State] + ): Option[StringConstancyInformation] = { + handleInterpretationResult(exprHandler.processDefSite(defSite)) + } + + protected def handleInterpretationResult(ep: EOptionP[Entity, StringConstancyProperty])(implicit + state: State + ): Option[StringConstancyInformation] = { + ep match { + case FinalP(p) => + Some(p.stringConstancyInformation) + case eps => + state.dependees = eps :: state.dependees + None + } + } +} 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 index f0c5fcbced..fca905af3d 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/DoubleValueInterpreter.scala @@ -6,7 +6,6 @@ package analyses package string_analysis package 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.br.fpcf.properties.string_definition.StringConstancyLevel @@ -18,10 +17,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class DoubleValueInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object DoubleValueInterpreter extends StringInterpreter[Nothing] { override type T = DoubleConst 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 index 4807dee087..f98b02aa06 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/FloatValueInterpreter.scala @@ -6,7 +6,6 @@ package analyses package string_analysis package 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.br.fpcf.properties.string_definition.StringConstancyLevel @@ -18,10 +17,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class FloatValueInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object FloatValueInterpreter extends StringInterpreter[Nothing] { override type T = FloatConst 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 f75d1f000b..27b742272d 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 @@ -7,7 +7,6 @@ package analyses package string_analysis package 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.br.fpcf.properties.string_definition.StringConstancyLevel @@ -19,10 +18,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class IntegerValueInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object IntegerValueInterpreter extends StringInterpreter[Nothing] { override type T = IntConst 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 1c8642fd66..4ca8241150 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 @@ -10,7 +10,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.ObjectType -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 @@ -18,10 +17,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) { - - protected val stmts: Array[Stmt[V]] = tac.stmts - protected val cfg: CFG[Stmt[V], TACStmts[V]] = tac.cfg +abstract class InterpretationHandler[State <: ComputationState[State]] { /** * A list of definition sites that have already been processed. Store it as a map for constant @@ -33,15 +29,9 @@ abstract class InterpretationHandler[State <: ComputationState[State]](tac: TAC) * 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) 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). + * @param defSite The definition site to process. Make sure that (1) the value is >= 0, (2) it actually exists, and + * (3) can be processed by one of the subclasses of [[StringInterpreter]] (in case (3) is violated, + * an [[IllegalArgumentException]] will be thrown). * @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 @@ -105,7 +95,7 @@ object InterpretationHandler { } else { if (expr.isVar) { val value = expr.asVar.value - value.isReferenceValue && value.asReferenceValue.upperTypeBound.exists { _.toJava == "java.lang.String" } + value.isReferenceValue && value.asReferenceValue.upperTypeBound.exists { _ == ObjectType.String } } else { false } @@ -121,12 +111,7 @@ object InterpretationHandler { ) /** - * 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]]. + * Checks whether an expression contains a call to [[StringBuilder#append]] or [[StringBuffer#append]]. */ def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { expr match { @@ -185,7 +170,7 @@ object InterpretationHandler { * 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 value The [[V]] 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. */ @@ -209,7 +194,7 @@ object InterpretationHandler { /** * Determines the [[New]] expressions that belongs to a given `duvar`. * - * @param duvar The [[org.opalj.tac.DUVar]] to get the [[New]]s for. + * @param value The [[V]] to get the [[New]]s for. * @param stmts The context to search in, e.g., the surrounding method. * @return Returns all found [[New]] expressions. */ 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 85956cfd52..323b6f1364 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 @@ -6,7 +6,6 @@ package analyses package string_analysis package interpretation -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FinalEP @@ -15,10 +14,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class NewInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object NewInterpreter extends StringInterpreter[Nothing] { override type T = New 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 7e76647c26..9b3b5eaeb5 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 @@ -6,26 +6,19 @@ package analyses package string_analysis package 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.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP -import org.opalj.tac.Stmt import org.opalj.tac.StringConst -import org.opalj.tac.TACStmts -import org.opalj.tac.V /** * Responsible for processing [[StringConst]]s. * * @author Maximilian Rüsch */ -case class StringConstInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends StringInterpreter[State] { +object StringConstInterpreter extends StringInterpreter[Nothing] { override type T = StringConst diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 48f989dd81..94137cebb9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -10,7 +10,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.Method -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.fpcf.Entity @@ -24,21 +23,6 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringInterpreter[State <: ComputationState[State]] { - /** - * The control flow graph that underlies the instruction to interpret. - */ - protected val cfg: CFG[Stmt[V], TACStmts[V]] - - /** - * Handles interpretation of instructions the current interpretation depends on. - * - * @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. - */ - protected val exprHandler: InterpretationHandler[State] - type T <: Any /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index da0ae4539f..1807e501ea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -78,11 +78,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis if (tacOpt.isEmpty) return Result(data, StringConstancyProperty.lb) // TODO add continuation - val tac = tacOpt.get val state = L0ComputationState(declaredMethods(data._2), data) - state.iHandler = L0InterpretationHandler(tac) - state.interimIHandler = L0InterpretationHandler(tac) - state.tac = tac + state.iHandler = L0InterpretationHandler() + state.interimIHandler = L0InterpretationHandler() + state.tac = tacOpt.get determinePossibleStrings(state) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala index e6638138ef..c1b98a29f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala @@ -7,12 +7,12 @@ package string_analysis package l0 package 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -21,14 +21,15 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0ArrayInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { + exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { + + implicit val _exprHandler: InterpretationHandler[State] = exprHandler override type T = ArrayLoad[V] override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val stmts = cfg.code.instructions + val stmts = state.tac.stmts val defSites = instr.arrayRef.asVar.definedBy.toArray var scis = Seq.empty[StringConstancyInformation] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala index 8d4ed29a48..c58f4fea61 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala @@ -7,12 +7,10 @@ package string_analysis package l0 package interpretation -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[GetField]]s. Currently, there is no support for fields, i.e., they are not analyzed but @@ -20,10 +18,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0GetFieldInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { +case class L0GetFieldInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { override type T = GetField[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala index 0fb76c25d2..ac09813d5b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala @@ -7,12 +7,10 @@ package string_analysis package l0 package interpretation -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `IntraproceduralGetStaticInterpreter` is responsible for processing @@ -22,10 +20,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -case class L0GetStaticInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { +case class L0GetStaticInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { override type T = GetStatic diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index c2e9cc06ba..c4363e4e41 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -33,13 +33,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt * * @author Maximilian Rüsch */ -class L0InterpretationHandler( - tac: TAC -)( +class L0InterpretationHandler()( implicit p: SomeProject, ps: PropertyStore -) extends InterpretationHandler[L0ComputationState](tac) { +) extends InterpretationHandler[L0ComputationState] { /** * Processed the given definition site in an intraprocedural fashion. @@ -71,38 +69,38 @@ class L0InterpretationHandler( processedDefSites(defSite) = () - stmts(defSite) match { + state.tac.stmts(defSite) match { case Assignment(_, _, expr: StringConst) => - StringConstInterpreter(cfg, this).interpret(expr) + StringConstInterpreter.interpret(expr) case Assignment(_, _, expr: IntConst) => - IntegerValueInterpreter(cfg, this).interpret(expr) + IntegerValueInterpreter.interpret(expr) case Assignment(_, _, expr: FloatConst) => - FloatValueInterpreter(cfg, this).interpret(expr) + FloatValueInterpreter.interpret(expr) case Assignment(_, _, expr: DoubleConst) => - DoubleValueInterpreter(cfg, this).interpret(expr) + DoubleValueInterpreter.interpret(expr) case Assignment(_, _, expr: BinaryExpr[V]) => - BinaryExprInterpreter(cfg, this).interpret(expr) + BinaryExprInterpreter.interpret(expr) case Assignment(_, _, expr: ArrayLoad[V]) => - L0ArrayInterpreter(cfg, this).interpret(expr, defSite) + L0ArrayInterpreter(this).interpret(expr, defSite)(state) case Assignment(_, _, expr: New) => - NewInterpreter(cfg, this).interpret(expr) + NewInterpreter.interpret(expr) case Assignment(_, _, expr: GetField[V]) => - L0GetFieldInterpreter(cfg, this).interpret(expr, defSite) + L0GetFieldInterpreter().interpret(expr, defSite)(state) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => // Currently unsupported FinalEP(expr, StringConstancyProperty.lb) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) + L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc, defSite) + L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) @@ -114,9 +112,9 @@ class L0InterpretationHandler( object L0InterpretationHandler { - def apply(tac: TAC)( + def apply()( implicit p: SomeProject, ps: PropertyStore - ): L0InterpretationHandler = new L0InterpretationHandler(tac) + ): L0InterpretationHandler = new L0InterpretationHandler } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index c9ca685b8b..d20a6b7ba6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -7,12 +7,12 @@ package string_analysis package l0 package 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.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -22,9 +22,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { + exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { + + implicit val _exprHandler: InterpretationHandler[State] = exprHandler override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index e30cd80d78..6c89cbba8e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -8,7 +8,6 @@ package l0 package interpretation import org.opalj.br.analyses.SomeProject -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.fpcf.Entity @@ -24,8 +23,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0StaticFunctionCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] )( implicit p: SomeProject, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index f1cca79d6a..a02f19a85f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -8,10 +8,8 @@ package l0 package interpretation import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** @@ -36,14 +34,4 @@ trait L0StringInterpreter[State <: ComputationState[State]] extends StringInterp * Thus, the entity needs to be replaced by the calling client. */ def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] - - protected def handleDependentDefSite(defSite: Int)(implicit state: State): Option[StringConstancyInformation] = { - exprHandler.processDefSite(defSite) match { - case FinalP(p) => - Some(p.stringConstancyInformation) - case eps => - state.dependees = eps :: state.dependees - None - } - } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 3c5ea80dc5..625541a2fd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -13,7 +13,6 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.DoubleType import org.opalj.br.FloatType import org.opalj.br.ObjectType -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 @@ -21,18 +20,20 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[VirtualFunctionCall]]s in an intraprocedural fashion. + * Responsible for processing [[VirtualFunctionCall]]s without a call graph. * The list of currently supported function calls can be seen in the documentation of [[interpret]]. * - * @author Patrick Mell + * @author Maximilian Rüsch */ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { + exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { + + implicit val _exprHandler: InterpretationHandler[State] = exprHandler override type T = VirtualFunctionCall[V] @@ -147,7 +148,7 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( // 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.isDefined && value.get.isTheNeutralElement) { - value = handleDependentDefSite(cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min) + value = handleDependentDefSite(state.tac.stmts(defSiteHead).asAssignment.targetVar.usedBy.toArray.min) } if (value.isEmpty) { @@ -182,20 +183,18 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( } /** - * 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. + * Processes 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])(implicit state: State ): Option[StringConstancyInformation] = { - handleDependentDefSite(call.receiver.asVar.definedBy.head) + handleInterpretationResult(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 the lower - * bound of [[StringConstancyProperty]]). + * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. (Currently, this function simply + * approximates `replace` functions by returning the lower bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall: StringConstancyInformation = InterpretationHandler.getStringConstancyInformationForReplace diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index d7e54efdd2..efedc9221f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -7,7 +7,6 @@ package string_analysis package l0 package 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.br.fpcf.properties.string_definition.StringConstancyLevel @@ -15,7 +14,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[VirtualMethodCall]]s in an intraprocedural fashion. @@ -23,10 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] { +case class L0VirtualMethodCallInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index bf00ceeed6..5a2a8d881f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -153,30 +153,16 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } if (state.iHandler == null) { - state.iHandler = L1InterpretationHandler( - state.tac, - ps, - project, - declaredFields, - fieldAccessInformation, - state, - contextProvider - ) + state.iHandler = + L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) 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 = L1InterpretationHandler( - state.tac, - ps, - project, - declaredFields, - fieldAccessInformation, - interimState, - contextProvider - ) + state.interimIHandler = + L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) } var requiresCallersInfo = false diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala index 1095ca783b..ecbea85cae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala @@ -9,7 +9,6 @@ package interpretation 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.collection.immutable.IntTrieSet @@ -20,7 +19,6 @@ 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.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler @@ -32,21 +30,18 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * interpretation. * For more information, see the [[interpret]] method. * - * @author Patrick Mell + * @author Maximilian Rüsch */ case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State], - state: State + exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { override type T = ArrayLoad[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 refinable result will have to be handled later on to - * not miss this information. + * @note This implementation will extend [[ComputationState.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 + * refinable result will have to be handled later on to not miss this information. */ override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 3406113627..f270598814 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -12,7 +12,6 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -25,7 +24,6 @@ import org.opalj.fpcf.PropertyStore import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis /** @@ -35,17 +33,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * @author Maximilian Rüsch */ case class L1FieldReadInterpreter[State <: ComputationState[State]]( - override protected val exprHandler: InterpretationHandler[State], - state: State, - ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, - project: SomeProject, - implicit val declaredFields: DeclaredFields, - implicit val contextProvider: ContextProvider + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + project: SomeProject, + implicit val declaredFields: DeclaredFields, + implicit val contextProvider: ContextProvider ) extends L1StringInterpreter[State] { - override protected val cfg: CFG[Stmt[V], TACStmts[V]] = state.tac.cfg - override type T = FieldRead[V] /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 67af32e638..e358ab3897 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -43,14 +43,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionC * @author Patrick Mell */ class L1InterpretationHandler( - tac: TAC, ps: PropertyStore, project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, - implicit val state: L1ComputationState, contextProvider: ContextProvider -) extends InterpretationHandler[L1ComputationState](tac) { +) extends InterpretationHandler[L1ComputationState] { /** * Processed the given definition site in an interprocedural fashion. @@ -86,7 +84,7 @@ class L1InterpretationHandler( // Note that def sites referring to constant expressions will be deleted further down processedDefSites(defSite) = () - stmts(defSite) match { + state.tac.stmts(defSite) match { case Assignment(_, _, expr: StringConst) => processConstExpr(expr, defSite) case Assignment(_, _, expr: IntConst) => processConstExpr(expr, defSite) case Assignment(_, _, expr: FloatConst) => processConstExpr(expr, defSite) @@ -121,12 +119,12 @@ class L1InterpretationHandler( private def processConstExpr( constExpr: SimpleValueConst, defSite: Int - ): FinalEP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): FinalEP[Entity, StringConstancyProperty] = { val finalEP = constExpr match { - case ic: IntConst => IntegerValueInterpreter(cfg, this).interpret(ic) - case fc: FloatConst => FloatValueInterpreter(cfg, this).interpret(fc) - case dc: DoubleConst => DoubleValueInterpreter(cfg, this).interpret(dc) - case sc: StringConst => StringConstInterpreter(cfg, this).interpret(sc) + case ic: IntConst => IntegerValueInterpreter.interpret(ic) + case fc: FloatConst => FloatValueInterpreter.interpret(fc) + case dc: DoubleConst => DoubleValueInterpreter.interpret(dc) + case sc: StringConst => StringConstInterpreter.interpret(sc) case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } val sci = finalEP.p.stringConstancyInformation @@ -143,11 +141,7 @@ class L1InterpretationHandler( expr: ArrayLoad[V], defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1ArrayAccessInterpreter( - cfg, - this, - state - ).interpret(expr, defSite) + val r = new L1ArrayAccessInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation } else { @@ -164,12 +158,8 @@ class L1InterpretationHandler( private def processNewArray( expr: NewArray[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1NewArrayInterpreter( - cfg, - this, - state - ).interpret(expr, defSite) + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + val r = new L1NewArrayInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation } else { @@ -183,8 +173,10 @@ class L1InterpretationHandler( /** * Helper / utility function for processing [[New]] expressions. */ - private def processNew(expr: New, defSite: Int): FinalEP[Entity, StringConstancyProperty] = { - val finalEP = NewInterpreter(cfg, this).interpret(expr) + private def processNew(expr: New, defSite: Int)(implicit + state: L1ComputationState + ): FinalEP[Entity, StringConstancyProperty] = { + val finalEP = NewInterpreter.interpret(expr) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) finalEP @@ -196,13 +188,8 @@ class L1InterpretationHandler( private def processVFC( expr: VirtualFunctionCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1VirtualFunctionCallInterpreter( - cfg, - this, - ps, - contextProvider - ).interpret(expr, defSite) + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + val r = new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).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 @@ -240,14 +227,8 @@ class L1InterpretationHandler( private def processStaticFunctionCall( expr: StaticFunctionCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1StaticFunctionCallInterpreter( - cfg, - this, - ps, - state, - contextProvider - ).interpret(expr, defSite) + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + val r = new L1StaticFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -259,9 +240,11 @@ class L1InterpretationHandler( /** * Helper / utility function for processing [[BinaryExpr]]s. */ - private def processBinaryExpr(expr: BinaryExpr[V], defSite: Int): FinalEP[Entity, StringConstancyProperty] = { + private def processBinaryExpr(expr: BinaryExpr[V], defSite: Int)(implicit + state: L1ComputationState + ): FinalEP[Entity, StringConstancyProperty] = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - val result = BinaryExprInterpreter(cfg, this).interpret(expr) + val result = BinaryExprInterpreter.interpret(expr) val sci = result.p.stringConstancyInformation state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) @@ -271,16 +254,11 @@ class L1InterpretationHandler( /** * Helper / utility function for processing [[GetField]]s. */ - private def processGetField(expr: FieldRead[V], defSite: Int): EOptionP[Entity, StringConstancyProperty] = { - val r = L1FieldReadInterpreter( - this, - state, - ps, - fieldAccessInformation, - project, - declaredFields, - contextProvider - ).interpret(expr, defSite) + private def processGetField(expr: FieldRead[V], defSite: Int)(implicit + state: L1ComputationState + ): EOptionP[Entity, StringConstancyProperty] = { + val r = L1FieldReadInterpreter(ps, fieldAccessInformation, project, declaredFields, contextProvider) + .interpret(expr, defSite)(state) if (r.isRefinable) { processedDefSites.remove(defSite) } @@ -295,12 +273,7 @@ class L1InterpretationHandler( expr: NonVirtualFunctionCall[V], defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val r = L1NonVirtualFunctionCallInterpreter( - cfg, - this, - ps, - contextProvider - ).interpret(expr, defSite) + val r = L1NonVirtualFunctionCallInterpreter(ps, contextProvider).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -315,7 +288,7 @@ class L1InterpretationHandler( expr: VirtualMethodCall[V], defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val r = L1VirtualMethodCallInterpreter(cfg, this).interpret(expr, defSite) + val r = L1VirtualMethodCallInterpreter().interpret(expr, defSite)(state) doInterimResultHandling(r, defSite) r } @@ -326,8 +299,8 @@ class L1InterpretationHandler( private def processNonVirtualMethodCall( nvmc: NonVirtualMethodCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val r = L1NonVirtualMethodCallInterpreter(cfg, this, state).interpret(nvmc, defSite) + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + val r = L1NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) @@ -344,7 +317,10 @@ class L1InterpretationHandler( * function handles the steps necessary to provide information for computing intermediate * results. */ - private def doInterimResultHandling(result: EOptionP[Entity, Property], defSite: Int): Unit = { + private def doInterimResultHandling( + result: EOptionP[Entity, Property], + defSite: Int + )(implicit state: L1ComputationState): Unit = { val sci = if (result.isFinal) { result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } else { @@ -364,7 +340,7 @@ class L1InterpretationHandler( reset = true ) } else { - stmts(defSite) match { + state.tac.stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] => NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) case Assignment(_, _, al: ArrayLoad[V]) => @@ -397,20 +373,16 @@ class L1InterpretationHandler( object L1InterpretationHandler { def apply( - tac: TAC, ps: PropertyStore, project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, - state: L1ComputationState, contextProvider: ContextProvider ): L1InterpretationHandler = new L1InterpretationHandler( - tac, ps, project, declaredFields, fieldAccessInformation, - state, contextProvider ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 4199acdb95..771979a744 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -7,7 +7,6 @@ package string_analysis package l1 package 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.fpcf.Entity @@ -26,15 +25,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ class L1NewArrayInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State], - state: State + exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { override type T = NewArray[V] /** - * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string + * @note This implementation will extend [[ComputationState.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 refinable result will have to be handled later on to * not miss this information. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index e8cf22411c..502b476f05 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -7,7 +7,6 @@ package string_analysis package l1 package interpretation -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.Entity @@ -15,7 +14,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[NonVirtualFunctionCall]]s in an interprocedural fashion. @@ -23,10 +21,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L1NonVirtualFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[L1ComputationState], - ps: PropertyStore, - contextProvider: ContextProvider + ps: PropertyStore, + contextProvider: ContextProvider ) extends L1StringInterpreter[L1ComputationState] { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 7951f6469a..0705731e55 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -7,7 +7,6 @@ package string_analysis package l1 package 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.fpcf.Entity @@ -22,9 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State], - state: State + exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 617631d6fc..7b12e61c8b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -10,7 +10,6 @@ package interpretation import scala.util.Try import org.opalj.br.ObjectType -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -23,17 +22,14 @@ import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[StaticFunctionCall]]s in an interprocedural fashion. - * For supported method calls, see the documentation of the `interpret` function. + * Responsible for processing [[StaticFunctionCall]]s with a call graph. * - * @author Patrick Mell + * @author Maximilian Rüsch */ class L1StaticFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[L1ComputationState], - ps: PropertyStore, - implicit val state: L1ComputationState, - contextProvider: ContextProvider + exprHandler: InterpretationHandler[L1ComputationState], + ps: PropertyStore, + contextProvider: ContextProvider ) extends L1StringInterpreter[L1ComputationState] { override type T = StaticFunctionCall[V] @@ -84,7 +80,7 @@ class L1StaticFunctionCallInterpreter( private def processArbitraryCall( instr: StaticFunctionCall[V], defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) // Static methods cannot be overwritten, thus diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 3700e432d6..1ed1b702b3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -15,10 +15,8 @@ import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext 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.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter @@ -66,16 +64,4 @@ trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterp (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) } - - protected def handleInterpretationResult(ep: EOptionP[Entity, StringConstancyProperty])(implicit - state: State - ): Option[StringConstancyInformation] = { - ep match { - case FinalP(p) => - Some(p.stringConstancyInformation) - case eps => - state.dependees = eps :: state.dependees - None - } - } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 52f01db56f..62145f9de5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -10,7 +10,6 @@ package interpretation import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.ObjectType -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -23,6 +22,7 @@ import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -32,11 +32,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ class L1VirtualFunctionCallInterpreter( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[L1ComputationState], - ps: PropertyStore, - contextProvider: ContextProvider -) extends L1StringInterpreter[L1ComputationState] { + exprHandler: InterpretationHandler[L1ComputationState], + ps: PropertyStore, + contextProvider: ContextProvider +) extends L1StringInterpreter[L1ComputationState] with DependingStringInterpreter[L1ComputationState] { override type T = VirtualFunctionCall[V] @@ -63,7 +62,7 @@ class L1VirtualFunctionCallInterpreter( * If none of the above-described cases match, a final result containing * [[StringConstancyProperty.lb]] is returned. * - * @note This function takes care of updating [[state.fpe2sci]] as necessary. + * @note This function takes care of updating [[ComputationState.fpe2sci]] as necessary. */ override def interpret(instr: T, defSite: Int)(implicit state: L1ComputationState @@ -290,7 +289,7 @@ class L1VirtualFunctionCallInterpreter( if (headSite < 0) { newValueSci = StringConstancyInformation.lb } else { - val ds = cfg.code.instructions(headSite).asAssignment.targetVar.usedBy.toArray.min + val ds = state.tac.stmts(headSite).asAssignment.targetVar.usedBy.toArray.min val r = exprHandler.processDefSite(ds) r match { case FinalP(p) => newValueSci = p.stringConstancyInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala index cc95a840ec..6768bf0966 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -7,13 +7,11 @@ package string_analysis package l1 package 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.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[VirtualMethodCall]]s in an interprocedural fashion. @@ -21,10 +19,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -case class L1VirtualMethodCallInterpreter[State <: ComputationState[State]]( - override protected val cfg: CFG[Stmt[V], TACStmts[V]], - override protected val exprHandler: InterpretationHandler[State] -) extends L1StringInterpreter[State] { +case class L1VirtualMethodCallInterpreter[State <: ComputationState[State]]() extends L1StringInterpreter[State] { override type T = VirtualMethodCall[V] From e25bd098ba126acd503e74278b065d36b95a7145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 11:58:41 +0100 Subject: [PATCH 356/583] Move array access handling to l0 --- .../InterproceduralTestMethods.java | 1 + .../interprocedural/StringProvider.java | 13 -- .../IntraProceduralTestMethods.java | 35 +++++ .../intraprocedural/StringProvider.java | 27 ++++ .../org/opalj/fpcf/StringAnalysisTest.scala | 9 +- .../L0ArrayAccessInterpreter.scala | 95 ++++++++++++++ .../interpretation/L0ArrayInterpreter.scala | 57 -------- .../L0InterpretationHandler.scala | 2 +- .../string_analysis/l1/L1StringAnalysis.scala | 4 +- .../l1/finalizer/ArrayLoadFinalizer.scala | 4 +- .../L1ArrayAccessInterpreter.scala | 122 ------------------ .../L1InterpretationHandler.scala | 3 +- 12 files changed, 171 insertions(+), 201 deletions(-) delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/StringProvider.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java index be4a7235e7..b03285f75b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java @@ -4,6 +4,7 @@ import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.HelloGreeting; import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods; +import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.StringProvider; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/StringProvider.java deleted file mode 100644 index 71826969da..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/StringProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.interprocedural; - -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/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java index 1eb242f507..357cdc1cf2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java @@ -117,6 +117,22 @@ public void simpleStringConcat() { analyzeString(className2); } + @StringDefinitionsCollection( + value = "checks if a string value with append(s) is determined correctly", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.String" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.Object" + ) + } + ) + public void simpleStringConcatWithStaticFunctionCalls() { + analyzeString(StringProvider.concat("java.lang.", "String")); + analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); + } + @StringDefinitionsCollection( value = "checks if a string value with > 2 continuous appends is determined correctly", stringDefinitions = { @@ -174,6 +190,25 @@ public void fromStringArray(int index) { } } + @StringDefinitionsCollection( + value = "a case where an array access needs to be interpreted with multiple static function calls", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(java.lang.Object|.*|java.lang.Integer|.*)" + ) + + }) + public void arrayStaticFunctionCalls(int i) { + String[] classes = { + "java.lang.Object", + getRuntimeClassName(), + StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), + System.getProperty("SomeClass") + }; + analyzeString(classes[i]); + } + @StringDefinitionsCollection( value = "a simple case where multiple definition sites have to be considered", stringDefinitions = { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java new file mode 100644 index 0000000000..acc6c192f6 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis.intraprocedural; + +public class StringProvider { + + /** + * Returns "[packageName].[className]". + */ + public static String getFQClassName(String packageName, String className) { + return packageName + "." + className; + } + + /** + * Returns "[packageName].[className]". + */ + public static String concat(String firstString, String secondString) { + return firstString + secondString; + } + + /** + * Returns "[packageName].[className]". + */ + public static String getFQClassNameWithStringBuilder(String packageName, String className) { + return (new StringBuilder()).append(packageName).append(".").append(className).toString(); + } + +} 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 72e319ce79..a5bf927e4d 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 @@ -114,7 +114,7 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods" override protected val nameTestMethod = "analyzeString" - override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/intraprocedural") + override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string_analysis/intraprocedural") override def init(p: Project[URL]): Unit = { val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] @@ -136,7 +136,7 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) - .filterNot(entity => entity._2.name.startsWith("simpleStringConcat")) + .filterNot(entity => entity._2.name == "simpleStringConcat") .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) @@ -161,7 +161,10 @@ class InterproceduralStringAnalysisTest extends StringAnalysisTest { override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.interprocedural.InterproceduralTestMethods" override protected val nameTestMethod = "analyzeString" - override def fixtureProjectPackage: List[String] = List(s"org/opalj/fpcf/fixtures/string_analysis/interprocedural") + override def fixtureProjectPackage: List[String] = List( + "org/opalj/fpcf/fixtures/string_analysis/intraprocedural", + "org/opalj/fpcf/fixtures/string_analysis/interprocedural" + ) override def init(p: Project[URL]): Unit = { p.get(RTACallGraphKey) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala new file mode 100644 index 0000000000..380e8e4cb6 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -0,0 +1,95 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.collection.immutable.IntTrieSet +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler + +/** + * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an intraprocedural fashion. + * + * @author Maximilian Rüsch + */ +case class L0ArrayAccessInterpreter[State <: ComputationState[State]]( + exprHandler: InterpretationHandler[State] +) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { + + implicit val _exprHandler: InterpretationHandler[State] = exprHandler + + override type T = ArrayLoad[V] + + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + implicit val stmts: Array[Stmt[V]] = state.tac.stmts + + val allDefSitesByPC = + L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr).map(ds => (pcOfDefSite(ds), ds)).toMap + val sciOpts = allDefSitesByPC.keys.toList.sorted.map { pc => + (pc, handleDependentDefSite(allDefSitesByPC(pc))) + }.map { + case (pc, sciOpt) => + if (sciOpt.isDefined) + state.appendToFpe2Sci(pc, sciOpt.get) + sciOpt + } + + // Add information of parameters + // TODO dont we have to incorporate parameter information into the scis? + instr.arrayRef.asVar.toPersistentForm.defPCs.filter(_ < 0).foreach { pc => + val paramPos = Math.abs(pc + 2) + val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) + state.appendToFpe2Sci(pc, sci) + } + + val unfinishedDependees = sciOpts.exists(_.isEmpty) + if (unfinishedDependees) { + // IMPROVE return interim here + FinalEP((instr.arrayRef.asVar, state.entity._2), StringConstancyProperty.lb) + } else { + var resultSci = StringConstancyInformation.reduceMultiple(sciOpts.map(_.get)) + if (resultSci.isTheNeutralElement) { + resultSci = StringConstancyInformation.lb + } + + state.appendToFpe2Sci(pcOfDefSite(defSite), resultSci) + FinalEP((instr.arrayRef.asVar, state.entity._2), StringConstancyProperty(resultSci)) + } + } +} + +object L0ArrayAccessInterpreter { + + type T = ArrayLoad[V] + + /** + * This function retrieves all definition sites of the array stores and array loads that belong to the given instruction. + * + * @return All definition sites associated with the array stores and array loads sorted in ascending order. + */ + def getStoreAndLoadDefSites(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { + var defSites = IntTrieSet.empty + instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => + stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { + stmts(_) match { + case ArrayStore(_, _, _, value) => + defSites = defSites ++ value.asVar.definedBy.toArray + case Assignment(_, _, expr: ArrayLoad[V]) => + defSites = defSites ++ expr.asArrayLoad.arrayRef.asVar.definedBy.toArray + case _ => + } + } + } + + defSites.toList.sorted + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala deleted file mode 100644 index c1b98a29f9..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayInterpreter.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l0 -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an intraprocedural fashion. - * - * @author Maximilian Rüsch - */ -case class L0ArrayInterpreter[State <: ComputationState[State]]( - exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { - - implicit val _exprHandler: InterpretationHandler[State] = exprHandler - - override type T = ArrayLoad[V] - - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val stmts = state.tac.stmts - val defSites = instr.arrayRef.asVar.definedBy.toArray - var scis = Seq.empty[StringConstancyInformation] - - defSites.filter(_ >= 0).sorted.foreach { defSite => - stmts(defSite).asAssignment.targetVar.usedBy.toArray.sorted.foreach { - stmts(_) match { - // Process ArrayStores - case ArrayStore(_, _, _, value) => - scis = scis ++ value.asVar.definedBy.toArray.sorted.flatMap { handleDependentDefSite } - // Process ArrayLoads - case Assignment(_, _, expr: ArrayLoad[V]) => - scis = scis ++ expr.arrayRef.asVar.definedBy.toArray.sorted.flatMap { handleDependentDefSite } - case _ => - } - } - } - - // In case it refers to a method parameter, add a dynamic string property - if (defSites.exists(_ < 0)) { - scis = scis :+ StringConstancyInformation.lb - } - - FinalEP(instr, StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis))) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index c4363e4e41..00bd3bb563 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -81,7 +81,7 @@ class L0InterpretationHandler()( case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr) case Assignment(_, _, expr: ArrayLoad[V]) => - L0ArrayInterpreter(this).interpret(expr, defSite)(state) + L0ArrayAccessInterpreter(this).interpret(expr, defSite)(state) case Assignment(_, _, expr: New) => NewInterpreter.interpret(expr) case Assignment(_, _, expr: GetField[V]) => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 5a2a8d881f..0af206ee9e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -36,7 +36,7 @@ import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -406,7 +406,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } override protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { - case al: ArrayLoad[V] => L1ArrayAccessInterpreter.getStoreAndLoadDefSites(al, tac.stmts).exists(_ < 0) + case al: ArrayLoad[V] => L0ArrayAccessInterpreter.getStoreAndLoadDefSites(al)(tac.stmts).exists(_ < 0) case _ => super.hasExprFormalParamUsage(expr) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala index 9d79651d22..458916e6bb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala @@ -10,7 +10,7 @@ package finalizer import scala.collection.mutable.ListBuffer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter /** * @author Maximilian Rüsch @@ -27,7 +27,7 @@ case class ArrayLoadFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) + val allDefSites = L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr)(state.tac.stmts) val allDefSitesByPC = allDefSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap allDefSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala deleted file mode 100644 index ecbea85cae..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1ArrayAccessInterpreter.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import scala.collection.mutable.ListBuffer - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.collection.immutable.IntTrieSet -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.tac.ArrayLoad -import org.opalj.tac.ArrayStore -import org.opalj.tac.Assignment -import org.opalj.tac.Stmt -import org.opalj.tac.V -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * 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. - * - * @author Maximilian Rüsch - */ -case class L1ArrayAccessInterpreter[State <: ComputationState[State]]( - exprHandler: InterpretationHandler[State] -) extends L1StringInterpreter[State] { - - override type T = ArrayLoad[V] - - /** - * @note This implementation will extend [[ComputationState.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 - * refinable result will have to be handled later on to not miss this information. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() - - val allDefSitesByPC = L1ArrayAccessInterpreter.getStoreAndLoadDefSites(instr, state.tac.stmts) - .map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - allDefSitesByPC.keys.map { pc => (pc, exprHandler.processDefSite(allDefSitesByPC(pc))) }.foreach { - case (pc, ep) => - if (ep.isFinal) - state.appendToFpe2Sci(pc, ep.asFinal.p.stringConstancyInformation) - results.append(ep) - } - - // Add information of parameters - instr.arrayRef.asVar.toPersistentForm(state.tac.stmts).defPCs.filter(_ < 0).foreach { pc => - val paramPos = Math.abs(pc + 2) - val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) - state.appendToFpe2Sci(pc, 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 interims = results.find(!_.isFinal) - if (interims.isDefined) { - interims.get - } else { - var resultSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.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(pcOfDefSite(defSite)(state.tac.stmts), resultSci) - results.head - } - } - -} - -object L1ArrayAccessInterpreter { - - 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 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, stmts: Array[Stmt[V]]): List[Int] = { - var defSites = IntTrieSet.empty - instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => - stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { - stmts(_) match { - case ArrayStore(_, _, _, value) => - defSites = defSites ++ value.asVar.definedBy.toArray - case Assignment(_, _, expr: ArrayLoad[V]) => - defSites = defSites ++ expr.asArrayLoad.arrayRef.asVar.definedBy.toArray - case _ => - } - } - } - - defSites.toList.sorted - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index e358ab3897..31a75aa03c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -26,6 +26,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueIn import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.GetFieldFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer @@ -141,7 +142,7 @@ class L1InterpretationHandler( expr: ArrayLoad[V], defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1ArrayAccessInterpreter(this).interpret(expr, defSite) + val r = new L0ArrayAccessInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation } else { From b6c4f630392d26c892ad08e6ed07a3137155acf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 13:18:31 +0100 Subject: [PATCH 357/583] Revamp testing scheme to level based testing --- .../L0TestMethods.java} | 10 +- .../StringProvider.java | 2 +- .../L1TestMethods.java} | 20 +-- .../hierarchies/GreetingService.java | 2 +- .../hierarchies/HelloGreeting.java | 2 +- .../hierarchies/SimpleHelloGreeting.java | 2 +- .../string_analysis/StringDefinitions.java | 4 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 141 +++++++++++------- 8 files changed, 105 insertions(+), 78 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{intraprocedural/IntraProceduralTestMethods.java => l0/L0TestMethods.java} (99%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{intraprocedural => l0}/StringProvider.java (91%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{interprocedural/InterproceduralTestMethods.java => l1/L1TestMethods.java} (97%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{interprocedural => l1}/hierarchies/GreetingService.java (64%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{interprocedural => l1}/hierarchies/HelloGreeting.java (73%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{interprocedural => l1}/hierarchies/SimpleHelloGreeting.java (73%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 357cdc1cf2..205a099c64 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/IntraProceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.intraprocedural; +package org.opalj.fpcf.fixtures.string_analysis.l0; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -15,8 +15,8 @@ import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; /** - * This file contains various tests for the LocalStringAnalysis. The following things are to be - * considered when adding test cases: + * This file contains various tests for the L0StringAnalysis. 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. @@ -57,14 +57,14 @@ * * @author Patrick Mell */ -public class IntraProceduralTestMethods { +public class L0TestMethods { 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.IntraproceduralStringAnalysisTest} to know which string read operation to + * {@link org.opalj.fpcf.L0StringAnalysisTest} 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/fixtures/string_analysis/intraprocedural/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java similarity index 91% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java index acc6c192f6..8d6263ed2f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/intraprocedural/StringProvider.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.intraprocedural; +package org.opalj.fpcf.fixtures.string_analysis.l0; public class StringProvider { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java similarity index 97% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index b03285f75b..9e88f2ece5 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.interprocedural; +package org.opalj.fpcf.fixtures.string_analysis.l1; -import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.GreetingService; -import org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies.HelloGreeting; -import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods; -import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.StringProvider; +import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.GreetingService; +import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.HelloGreeting; +import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; +import org.opalj.fpcf.fixtures.string_analysis.l0.StringProvider; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -17,12 +17,12 @@ import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; /** - * This file contains various tests for the InterproceduralStringAnalysis. For further information - * on what to consider, please see {@link IntraProceduralTestMethods} + * This file contains various tests for the L1StringAnalysis. For further information on what to consider, please see + * {@link L0TestMethods}. * * @author Patrick Mell */ -public class InterproceduralTestMethods { +public class L1TestMethods extends L0TestMethods { public static final String JAVA_LANG = "java.lang"; private static final String rmiServerImplStubClassName = @@ -45,12 +45,12 @@ public class InterproceduralTestMethods { private String[] monthNames = { "January", "February", "March", getApril() }; /** - * {@see LocalTestMethods#analyzeString} + * {@see L0TestMethods#analyzeString} */ public void analyzeString(String s) { } - public InterproceduralTestMethods(float e) { + public L1TestMethods(float e) { fieldWithConstructorInit = "initialized by constructor"; secretNumber = e; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/GreetingService.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java similarity index 64% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/GreetingService.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java index efa32e9d7e..7a01b47f17 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/GreetingService.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; public interface GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/HelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java similarity index 73% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/HelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java index c17d033516..dec83fbf2a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/HelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; public class HelloGreeting implements GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/SimpleHelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java similarity index 73% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/SimpleHelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java index e0e6db9229..dddd15c907 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/interprocedural/hierarchies/SimpleHelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.interprocedural.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; public class SimpleHelloGreeting implements GreetingService { 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 de77ccd8a9..41d581f17a 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 @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.properties.string_analysis; -import org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods; +import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; import org.opalj.fpcf.properties.PropertyValidator; import java.lang.annotation.*; @@ -33,7 +33,7 @@ /** * A regexp like string that describes the element(s) that are expected. For the rules, refer to - * {@link IntraProceduralTestMethods}. + * {@link L0TestMethods}. * 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/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index a5bf927e4d..27e79df0a6 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 @@ -2,13 +2,14 @@ package org.opalj package fpcf +import java.net.URL + import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse import org.opalj.ai.fpcf.properties.AIDomainFactoryKey - -import java.net.URL import org.opalj.br.Annotation import org.opalj.br.Annotations import org.opalj.br.Method +import org.opalj.br.ObjectType import org.opalj.br.analyses.Project import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey @@ -23,50 +24,54 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis sealed abstract class StringAnalysisTest extends PropertiesTest { - // The fully-qualified name of the class that contains the test methods. - protected def fqTestMethodsClass: String // The name of the method from which to extract PUVars to analyze. - protected def nameTestMethod: String + val nameTestMethod: String = "analyzeString" + + def level: Int + + override def fixtureProjectPackage: List[String] = { + StringAnalysisTest.getFixtureProjectPackages(level).toList + } + + protected def allowedFQTestMethodsClassNames: Iterable[String] = { + StringAnalysisTest.getAllowedFQTestMethodClassNamesUntilLevel(level) + } /** - * 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. + * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, + * [[extractPUVars]] is called with their [[TACode]]. */ - def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = - a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation - def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(SEntity, Method)] = { - var entitiesToAnalyze = Seq[(SEntity, Method)]() val tacProvider = project.get(EagerDetachedTACAIKey) - project.allMethodsWithBody.filter { - _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => - exists || StringAnalysisTest.isStringUsageAnnotation(a) - ) - } foreach { m => - entitiesToAnalyze = entitiesToAnalyze ++ extractPUVars(tacProvider(m)).map((_, m)) - } - entitiesToAnalyze + project.classHierarchy.allSuperclassesIterator( + ObjectType(StringAnalysisTest.getAllowedFQTestMethodObjectTypeNameForLevel(level)), + reflexive = true + )(project).toList + .filter(_.thisType.packageName.startsWith("org/opalj/fpcf/fixtures/string_analysis/")) + .sortBy { cf => cf.thisType.simpleName.substring(1, 2).toInt } + .foldRight(Seq.empty[Method]) { (cf, methods) => + methods ++ cf.methods.filterNot(m => methods.exists(_.name == m.name)) + } + .filter { + _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => + exists || StringAnalysisTest.isStringUsageAnnotation(a) + ) + } + .foldLeft(Seq.empty[(SEntity, Method)]) { (entities, m) => + entities ++ extractPUVars(tacProvider(m)).map((_, m)) + } } /** - * Extracts [[org.opalj.tac.PUVar]]s from a set of statements. The locations of the PUVar are - * identified by the argument to the very first call to [[fqTestMethodsClass]]#[[nameTestMethod]]. + * Extracts [[org.opalj.tac.PUVar]]s from a set of statements. The locations of the [[org.opalj.tac.PUVar]]s are + * identified by the argument to the very first call to [[nameTestMethod]]. * - * @param tac The tac from which to extract the PUVar, usually derived from the - * method that contains the call(s) to [[fqTestMethodsClass]]#[[nameTestMethod]]. - * @return Returns the arguments of the [[fqTestMethodsClass]]#[[nameTestMethod]] as a PUVars list in the - * order in which they occurred in the given statements. + * @return Returns the arguments of the [[nameTestMethod]] as a PUVars list in the order in which they occurred. */ def extractPUVars(tac: TACode[TACMethodParameter, V]): List[SEntity] = { tac.cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) => - declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod + allowedFQTestMethodsClassNames.exists(_ == declClass.toJavaClass.getName) && name == nameTestMethod case _ => false }.map(_.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)).toList } @@ -83,7 +88,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { Tuple3( (puVar, am._1), { s: String => s"${am._2(s)} (#$index)" }, - List(getStringDefinitionsFromCollection(am._3, index)) + List(StringAnalysisTest.getStringDefinitionsFromCollection(am._3, index)) ) } } @@ -92,6 +97,18 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { object StringAnalysisTest { + def getFixtureProjectPackages(level: Int): Seq[String] = { + Range.inclusive(0, level).map(l => s"org/opalj/fpcf/fixtures/string_analysis/l$l") + } + + def getAllowedFQTestMethodClassNamesUntilLevel(level: Int): Seq[String] = { + Range.inclusive(0, level).map(l => s"org.opalj.fpcf.fixtures.string_analysis.l$l.L${l}TestMethods") + } + + def getAllowedFQTestMethodObjectTypeNameForLevel(level: Int): String = { + s"org/opalj/fpcf/fixtures/string_analysis/l$level/L${level}TestMethods" + } + /** * Takes an annotation and checks if it is a * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]] annotation. @@ -101,6 +118,19 @@ object StringAnalysisTest { */ def isStringUsageAnnotation(a: Annotation): Boolean = a.annotationType.toJavaClass.getName == "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" + + /** + * 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. + */ + def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = + a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation } /** @@ -109,30 +139,27 @@ object StringAnalysisTest { * * @author Maximilian Rüsch */ -class IntraproceduralStringAnalysisTest extends StringAnalysisTest { - - override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.intraprocedural.IntraProceduralTestMethods" - override protected val nameTestMethod = "analyzeString" +class L0StringAnalysisTest extends StringAnalysisTest { - override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string_analysis/intraprocedural") + override def level = 0 override def init(p: Project[URL]): Unit = { val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => Set(domain) + case None => Set(domain) case Some(requirements) => requirements + domain } p.get(RTACallGraphKey) } - describe("the org.opalj.fpcf.IntraproceduralStringAnalysis is started") { + describe("the org.opalj.fpcf.L0StringAnalysis is started") { val as = executeAnalyses(LazyL0StringAnalysis) val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities - //.filter(entity => entity._2.name.startsWith("tryCatchFinally")) - //.filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + // .filter(entity => entity._2.name.startsWith("tryCatchFinally")) + // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) @@ -140,13 +167,13 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) - //it("can be executed without exceptions") { - newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + // it("can be executed without exceptions") { + newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) - as.propertyStore.shutdown() + as.propertyStore.shutdown() - validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) - //} + validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) + // } } } @@ -156,24 +183,24 @@ class IntraproceduralStringAnalysisTest extends StringAnalysisTest { * * @author Maximilian Rüsch */ -class InterproceduralStringAnalysisTest extends StringAnalysisTest { - - override protected val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.interprocedural.InterproceduralTestMethods" - override protected val nameTestMethod = "analyzeString" +class L1StringAnalysisTest extends StringAnalysisTest { - override def fixtureProjectPackage: List[String] = List( - "org/opalj/fpcf/fixtures/string_analysis/intraprocedural", - "org/opalj/fpcf/fixtures/string_analysis/interprocedural" - ) + override def level = 1 override def init(p: Project[URL]): Unit = { p.get(RTACallGraphKey) } - describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { + describe("the org.opalj.fpcf.L1StringAnalysis is started") { val as = executeAnalyses(LazyL1StringAnalysis) - val entities = determineEntitiesToAnalyze(as.project) //.filter(entity => entity._2.name == "valueOfTest2") + val entities = determineEntitiesToAnalyze(as.project) + .filterNot(entity => entity._2.name.startsWith("switchNested")) + .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) + .filterNot(entity => entity._2.name == "simpleStringConcat") + .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) + .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.shutdown() From cd34702418d6fc926b74a46745a511ab0c296bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 14:38:08 +0100 Subject: [PATCH 358/583] Add static method test --- .../fixtures/string_analysis/l0/L0TestMethods.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 205a099c64..3bdb00bae0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1201,6 +1201,18 @@ public void unknownCharValue() { 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 fromStaticMethodWithParamTest() { + analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } From 800e5c673b039e9ba9973eddfe2915d967b20de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 14:44:13 +0100 Subject: [PATCH 359/583] Simplify L1Analysis Fixes unknown char value test --- .../string_analysis/l1/L1StringAnalysis.scala | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 0af206ee9e..6c20c10992 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -155,12 +155,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.iHandler == null) { state.iHandler = L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) - 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 = L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) } @@ -262,26 +256,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { && state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath)(state) ) { - // 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 fpe: FlatPathElement => - state.fpe2sci.contains(fpe.pc) && state.fpe2sci(fpe.pc).length == 1 && - state.fpe2sci(fpe.pc).head == StringConstancyInformation.getNeutralElement - case _ => false - } - } else false - - sci = if (isEmptyString) { - StringConstancyInformation.getNeutralElement - } else { - new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, - state.fpe2sci - )(state).reduce(true) - } + sci = new PathTransformer(state.iHandler) + .pathToStringTree(state.computedLeanPath, state.fpe2sci) + .reduce(true) } if (state.dependees.nonEmpty) { @@ -295,11 +272,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { /** * 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. - * @return Returns a final result if (already) available. Otherwise, an intermediate result will - * be returned. + * @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. + * @return Returns a final result if (already) available. Otherwise, an intermediate result will be returned. */ override protected def continuation( state: L1ComputationState From e232e4dc5711ee8f639f23dd9a11a635feecab90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 15:04:05 +0100 Subject: [PATCH 360/583] Simplify virtual function call resolving Fixes while with break test --- .../L0VirtualFunctionCallInterpreter.scala | 41 ++++++---- .../L1VirtualFunctionCallInterpreter.scala | 75 ++++++------------- 2 files changed, 48 insertions(+), 68 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 625541a2fd..bfe27e00cb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -37,37 +37,48 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( override type T = VirtualFunctionCall[V] + override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + val result = handleInterpretation(instr, defSite) + + if (result.isDefined) { + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.get) + } + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(result.getOrElse(StringConstancyInformation.lb))) + } + /** * 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. + * `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 - * [[L0VirtualFunctionCallInterpreter.interpretReplaceCall]]. + * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For + * further information how this operation is processed, see + * [[L0VirtualFunctionCallInterpreter.interpretReplaceCall]]. *
      • *
      • - * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] - * will be returned in case the passed method returns a [[java.lang.String]]. + * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] + * will be returned in case the passed method returns a [[java.lang.String]]. *
      • *
      * * If none of the above-described cases match, a result containing * [[StringConstancyProperty.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val sci = instr.name match { + protected def handleInterpretation(instr: T, defSite: Int)(implicit + state: State + ): Option[StringConstancyInformation] = { + instr.name match { case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) case "replace" => Some(interpretReplaceCall) case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj.fqn == "java/lang/String" => Some(StringConstancyInformation.lb) + case obj: ObjectType if obj == ObjectType.String => Some(StringConstancyInformation.lb) case FloatType | DoubleType => Some(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, @@ -76,8 +87,6 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( case _ => Some(StringConstancyInformation.getNeutralElement) } } - - FinalEP(instr, StringConstancyProperty(sci.get)) } /** @@ -88,7 +97,7 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( private def interpretAppendCall(appendCall: VirtualFunctionCall[V])(implicit state: State ): Option[StringConstancyInformation] = { - val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation + val receiverSci = receiverValuesOfAppendCall(appendCall) val appendSci = valueOfAppendCall(appendCall) if (appendSci.isEmpty) { @@ -124,14 +133,14 @@ case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( */ private def receiverValuesOfAppendCall(call: VirtualFunctionCall[V])(implicit state: State - ): StringConstancyProperty = { + ): 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 val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => val r = exprHandler.processDefSite(ds) r.asFinal.p.stringConstancyInformation }.filter { sci => !sci.isTheNeutralElement } - StringConstancyProperty(scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement)) + scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 62145f9de5..b2d8bc4cf3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -22,11 +22,11 @@ import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter /** - * Responsible for processing [[VirtualFunctionCall]]s in an interprocedural fashion. + * Responsible for processing [[VirtualFunctionCall]]s with a call graph where applicable. * The list of currently supported function calls can be seen in the documentation of [[interpret]]. * * @author Patrick Mell @@ -35,7 +35,8 @@ class L1VirtualFunctionCallInterpreter( exprHandler: InterpretationHandler[L1ComputationState], ps: PropertyStore, contextProvider: ContextProvider -) extends L1StringInterpreter[L1ComputationState] with DependingStringInterpreter[L1ComputationState] { +) extends L0VirtualFunctionCallInterpreter[L1ComputationState](exprHandler) + with L1StringInterpreter[L1ComputationState] { override type T = VirtualFunctionCall[V] @@ -64,26 +65,21 @@ class L1VirtualFunctionCallInterpreter( * * @note This function takes care of updating [[ComputationState.fpe2sci]] as necessary. */ - override def interpret(instr: T, defSite: Int)(implicit + + override protected def handleInterpretation(instr: T, defSite: Int)(implicit state: L1ComputationState - ): EOptionP[Entity, StringConstancyProperty] = { - val result = instr.name match { - case "append" => interpretAppendCall(instr) - case "toString" => interpretToStringCall(instr) - case "replace" => Some(interpretReplaceCall) + ): Option[StringConstancyInformation] = { + instr.name match { + case "append" => interpretAppendCall(instr) + case "toString" | "replace" => super.handleInterpretation(instr, defSite) case _ => instr.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => interpretArbitraryCall(instr, defSite) case _ => - Some(StringConstancyInformation.lb) + super.handleInterpretation(instr, defSite) } } - - if (result.isDefined) { - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.get) - } - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(result.getOrElse(StringConstancyInformation.lb))) } /** @@ -184,10 +180,7 @@ class L1VirtualFunctionCallInterpreter( val receiverResults = receiverValuesOfAppendCall(appendCall) val appendResult = valueOfAppendCall(appendCall) - // If there is an intermediate result, return this one (then the final result cannot yet be computed) - if (receiverResults.head.isRefinable) { - return None - } else if (appendResult.isRefinable) { + if (receiverResults.head.isRefinable || appendResult.isRefinable) { return None } @@ -233,8 +226,8 @@ class L1VirtualFunctionCallInterpreter( * not be computed. Otherwise, the result list will contain >= 1 elements of type [[FinalEP]] * 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]]. + * @note All final results computed by this function are put int [[ComputationState.fpe2sci]] even if the returned + * list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] @@ -281,24 +274,20 @@ class L1VirtualFunctionCallInterpreter( val sciValues = values.map { _.asFinal.p.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 - var newValueSci = StringConstancyInformation.getNeutralElement - if (defSitesValueSci.isTheNeutralElement) { - val headSite = defSites.head - if (headSite < 0) { - newValueSci = StringConstancyInformation.lb + // If defSiteHead points to a "New", value will be the empty list. In that case, process the first use site + val newValueSci = if (defSitesValueSci.isTheNeutralElement) { + if (defSites.head < 0) { + StringConstancyInformation.lb } else { - val ds = state.tac.stmts(headSite).asAssignment.targetVar.usedBy.toArray.min - val r = exprHandler.processDefSite(ds) - r match { - case FinalP(p) => newValueSci = p.stringConstancyInformation + val ds = state.tac.stmts(defSites.head).asAssignment.targetVar.usedBy.toArray.min + exprHandler.processDefSite(ds) match { + case FinalP(p) => p.stringConstancyInformation // Defer the computation if there is no final result yet - case _ => return r + case interimEP => return interimEP } } } else { - newValueSci = defSitesValueSci + defSitesValueSci } val finalSci = param.value.computationalType match { @@ -336,24 +325,6 @@ class L1VirtualFunctionCallInterpreter( FinalEP(e, StringConstancyProperty(finalSci)) } - /** - * 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])( - implicit state: L1ComputationState - ): Option[StringConstancyInformation] = - handleInterpretationResult(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 the lower - * bound of [[StringConstancyProperty]]). - */ - private def interpretReplaceCall: StringConstancyInformation = - InterpretationHandler.getStringConstancyInformationForReplace - /** * Checks whether a given string is an integer value, i.e. contains only numbers. */ From a5611169e8f2c2497e1b3f953cd6456c2d746a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 15:13:48 +0100 Subject: [PATCH 361/583] Support simple contexts for callees via context provider Fixes several context sensitive tests --- .../string_analysis/l1/L1ComputationState.scala | 4 +++- .../string_analysis/l1/L1StringAnalysis.scala | 3 ++- .../L1NonVirtualFunctionCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 5 ++--- .../l1/interpretation/L1StringInterpreter.scala | 6 +++--- .../L1VirtualFunctionCallInterpreter.scala | 11 +++++------ 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index e7f25be135..1c71eaaa3e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -7,6 +7,7 @@ package string_analysis package l1 import org.opalj.br.DeclaredMethod +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers @@ -19,7 +20,8 @@ import org.opalj.br.fpcf.properties.cg.Callers */ case class L1ComputationState( override val dm: DeclaredMethod, - override val entity: SContext + override val entity: SContext, + methodContext: Context ) extends ComputationState[L1ComputationState] { /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 6c20c10992..2aaed33bc4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -106,7 +106,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) - val state = L1ComputationState(dm, data) + // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) + val state = L1ComputationState(dm, data, contextProvider.newContext(declaredMethods(data._2))) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 502b476f05..f62fff7e1b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -30,7 +30,7 @@ case class L1NonVirtualFunctionCallInterpreter( override def interpret(instr: T, defSite: Int)(implicit state: L1ComputationState ): EOptionP[Entity, StringConstancyProperty] = { - val methods = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) + val methods = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) if (methods._1.isEmpty) { // No methods available => Return lower bound return FinalEP(instr, StringConstancyProperty.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 7b12e61c8b..00ddddbdd8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -11,7 +11,6 @@ import scala.util.Try import org.opalj.br.ObjectType import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity @@ -81,7 +80,7 @@ class L1StaticFunctionCallInterpreter( instr: StaticFunctionCall[V], defSite: Int )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) + val methods, _ = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) // Static methods cannot be overwritten, thus // 1) we do not need the second return value of getMethodsForPC and @@ -94,7 +93,7 @@ class L1StaticFunctionCallInterpreter( val m = methods._1.head val (_, tac) = getTACAI(ps, m, state) - val directCallSites = state.callees.directCallSites(NoContext)(ps, contextProvider) + val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) val relevantPCs = directCallSites.filter { case (_, calledMethods) => calledMethods.exists(m => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 1ed1b702b3..d819a5b0a4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -12,7 +12,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.NoContext +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.Entity @@ -48,7 +48,7 @@ trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterp * second return value indicates whether at least one method has an unknown body (if `true`, * then there is such a method). */ - protected def getMethodsForPC(pc: Int)( + protected def getMethodsForPC(context: Context, pc: Int)( implicit ps: PropertyStore, callees: Callees, @@ -57,7 +57,7 @@ trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterp var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() - callees.callees(NoContext, pc)(ps, contextProvider).map(_.method).foreach { + callees.callees(context, pc)(ps, contextProvider).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => hasMethodWithUnknownBody = true } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index b2d8bc4cf3..ab29a38639 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -11,7 +11,6 @@ import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.ObjectType import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.NoContext 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 @@ -32,9 +31,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFu * @author Patrick Mell */ class L1VirtualFunctionCallInterpreter( - exprHandler: InterpretationHandler[L1ComputationState], - ps: PropertyStore, - contextProvider: ContextProvider + exprHandler: InterpretationHandler[L1ComputationState], + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider ) extends L0VirtualFunctionCallInterpreter[L1ComputationState](exprHandler) with L1StringInterpreter[L1ComputationState] { @@ -91,13 +90,13 @@ class L1VirtualFunctionCallInterpreter( private def interpretArbitraryCall(instr: T, defSite: Int)( implicit state: L1ComputationState ): Option[StringConstancyInformation] = { - val (methods, _) = getMethodsForPC(instr.pc)(ps, state.callees, contextProvider) + val (methods, _) = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) if (methods.isEmpty) { return Some(StringConstancyInformation.lb) } // TODO: Type Iterator! - val directCallSites = state.callees.directCallSites(NoContext)(ps, contextProvider) + val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava val relevantPCs = directCallSites.filter { From c296ec33f430b2218d73a934c8203248526ff6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 15:53:39 +0100 Subject: [PATCH 362/583] Revamp state hierarchy --- .../InterpretationHandler.scala | 2 +- .../string_analysis/l0/L0StringAnalysis.scala | 19 +++-- .../L0ArrayAccessInterpreter.scala | 2 +- .../L0GetFieldInterpreter.scala | 2 +- .../L0GetStaticInterpreter.scala | 2 +- .../L0InterpretationHandler.scala | 12 ++-- .../L0NonVirtualMethodCallInterpreter.scala | 2 +- .../L0StaticFunctionCallInterpreter.scala | 2 +- .../interpretation/L0StringInterpreter.scala | 2 +- .../L0VirtualFunctionCallInterpreter.scala | 2 +- .../L0VirtualMethodCallInterpreter.scala | 2 +- .../l1/L1ComputationState.scala | 17 ++--- .../string_analysis/l1/L1StringAnalysis.scala | 23 ++++-- .../l1/finalizer/ArrayLoadFinalizer.scala | 8 +-- ...nalizer.scala => FieldReadFinalizer.scala} | 6 +- .../l1/finalizer/L1Finalizer.scala | 11 +-- .../l1/finalizer/NewArrayFinalizer.scala | 6 +- .../NonVirtualMethodCallFinalizer.scala | 8 +-- .../StaticFunctionCallFinalizer.scala | 8 +-- .../VirtualFunctionCallFinalizer.scala | 16 ++--- .../L1FieldReadInterpreter.scala | 2 +- .../L1InterpretationHandler.scala | 72 +++++++++---------- .../L1NewArrayInterpreter.scala | 2 +- .../L1NonVirtualFunctionCallInterpreter.scala | 12 ++-- .../L1NonVirtualMethodCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 18 ++--- .../interpretation/L1StringInterpreter.scala | 10 ++- .../L1VirtualFunctionCallInterpreter.scala | 20 +++--- .../L1VirtualMethodCallInterpreter.scala | 2 +- 29 files changed, 134 insertions(+), 158 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/{GetFieldFinalizer.scala => FieldReadFinalizer.scala} (73%) 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 4ca8241150..3f18041583 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 @@ -57,7 +57,7 @@ abstract class InterpretationHandler[State <: ComputationState[State]] { /** * Finalized a given definition state. */ - def finalizeDefSite(defSite: Int, state: State): Unit + def finalizeDefSite(defSite: Int)(implicit state: State): Unit = {} /** * This function takes parameters and a definition site and extracts the desired parameter from diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 1807e501ea..7c7bf4e02f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -29,15 +29,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI -/** - * 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. - */ -protected[l0] case class L0ComputationState( - override val dm: DeclaredMethod, - override val entity: SContext -) extends ComputationState[L0ComputationState] +trait L0ComputationState[State <: L0ComputationState[State]] extends ComputationState[State] /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program @@ -66,7 +58,12 @@ protected[l0] case class L0ComputationState( */ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - override type State = L0ComputationState + protected[l0] case class CState( + override val dm: DeclaredMethod, + override val entity: SContext + ) extends L0ComputationState[CState] + + override type State = CState def analyze(data: SContext): ProperPropertyComputationResult = { // Retrieve TAC from property store @@ -78,7 +75,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis if (tacOpt.isEmpty) return Result(data, StringConstancyProperty.lb) // TODO add continuation - val state = L0ComputationState(declaredMethods(data._2), data) + val state = CState(declaredMethods(data._2), data) state.iHandler = L0InterpretationHandler() state.interimIHandler = L0InterpretationHandler() state.tac = tacOpt.get diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 380e8e4cb6..999e00c086 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0ArrayAccessInterpreter[State <: ComputationState[State]]( +case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala index c58f4fea61..c0d44a0dc8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala @@ -18,7 +18,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class L0GetFieldInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { +case class L0GetFieldInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { override type T = GetField[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala index ac09813d5b..23a4a570cc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -case class L0GetStaticInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { +case class L0GetStaticInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { override type T = GetStatic diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 00bd3bb563..e862bc077b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -33,11 +33,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt * * @author Maximilian Rüsch */ -class L0InterpretationHandler()( +class L0InterpretationHandler[State <: L0ComputationState[State]]()( implicit p: SomeProject, ps: PropertyStore -) extends InterpretationHandler[L0ComputationState] { +) extends InterpretationHandler[State] { /** * Processed the given definition site in an intraprocedural fashion. @@ -45,7 +45,7 @@ class L0InterpretationHandler()( * @inheritdoc */ override def processDefSite(defSite: Int)(implicit - state: L0ComputationState + state: State ): 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" @@ -106,15 +106,13 @@ class L0InterpretationHandler()( FinalEP(e, StringConstancyProperty.getNeutralElement) } } - - override def finalizeDefSite(defSite: Int, state: L0ComputationState): Unit = {} } object L0InterpretationHandler { - def apply()( + def apply[State <: L0ComputationState[State]]()( implicit p: SomeProject, ps: PropertyStore - ): L0InterpretationHandler = new L0InterpretationHandler + ): L0InterpretationHandler[State] = new L0InterpretationHandler[State] } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index d20a6b7ba6..3f330cf4f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( +case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 6c89cbba8e..c7cba67307 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -22,7 +22,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter[State <: ComputationState[State]]( +case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] )( implicit diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index a02f19a85f..2d7d47982b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -15,7 +15,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpre /** * @author Maximilian Rüsch */ -trait L0StringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { +trait L0StringInterpreter[State <: L0ComputationState[State]] extends StringInterpreter[State] { /** * @param instr The instruction that is to be interpreted. It is the responsibility of implementations to make sure diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index bfe27e00cb..9eecfe89a4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -29,7 +29,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0VirtualFunctionCallInterpreter[State <: ComputationState[State]]( +case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index efedc9221f..a6ec6dd9b6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.fpcf.FinalEP * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: ComputationState[State]]() extends L0StringInterpreter[State] { +case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index 1c71eaaa3e..6d64522a4a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -6,23 +6,14 @@ package analyses package string_analysis package l1 -import org.opalj.br.DeclaredMethod import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState -/** - * 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 L1ComputationState( - override val dm: DeclaredMethod, - override val entity: SContext, - methodContext: Context -) extends ComputationState[L1ComputationState] { +trait L1ComputationState[State <: L1ComputationState[State]] extends L0ComputationState[State] { + + val methodContext: Context /** * Callees information regarding the declared method that corresponds to the entity's method diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 2aaed33bc4..ac14f67213 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -8,7 +8,9 @@ package l1 import scala.collection.mutable.ListBuffer +import org.opalj.br.DeclaredMethod import org.opalj.br.FieldType +import org.opalj.br.Method import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey @@ -21,6 +23,7 @@ import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers @@ -71,7 +74,13 @@ import org.opalj.tac.fpcf.properties.TACAI */ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - override type State = L1ComputationState + protected[l1] case class CState( + override val dm: DeclaredMethod, + override val entity: (SEntity, Method), + override val methodContext: Context + ) extends L1ComputationState[CState] + + override type State = CState /** * To analyze an expression within a method ''m'', callers information might be necessary, e.g., @@ -107,7 +116,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) - val state = L1ComputationState(dm, data, contextProvider.newContext(declaredMethods(data._2))) + val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { @@ -155,9 +164,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.iHandler == null) { state.iHandler = - L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) + L1InterpretationHandler(project, declaredFields, fieldAccessInformation, ps, contextProvider) state.interimIHandler = - L1InterpretationHandler(ps, project, declaredFields, fieldAccessInformation, contextProvider) + L1InterpretationHandler(project, declaredFields, fieldAccessInformation, ps, contextProvider) } var requiresCallersInfo = false @@ -278,7 +287,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * @return Returns a final result if (already) available. Otherwise, an intermediate result will be returned. */ override protected def continuation( - state: L1ComputationState + state: State )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != eps.e) @@ -310,7 +319,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { ): Unit = path.elements.foreach { case fpe: FlatPathElement => if (!state.fpe2sci.contains(fpe.pc)) { - iHandler.finalizeDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get, state) + iHandler.finalizeDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get)(state) } case npe: NestedPathElement => finalizePreparations(Path(npe.element.toList), state, iHandler) @@ -322,7 +331,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * analysis. These interpretations are registered using [[StringAnalysis.registerParams]]. The return value of this * function indicates whether the parameter evaluation is done (`true`) or not yet (`false`). */ - private def registerParams(state: L1ComputationState): Boolean = { + private def registerParams(state: State): Boolean = { val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq if (callers.length > callersThreshold) { state.params.append( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala index 458916e6bb..14e39fd4ba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala @@ -15,9 +15,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAcce /** * @author Maximilian Rüsch */ -case class ArrayLoadFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class ArrayLoadFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override type T = ArrayLoad[V] @@ -26,12 +24,12 @@ case class ArrayLoadFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { val allDefSites = L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr)(state.tac.stmts) val allDefSitesByPC = allDefSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap allDefSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(allDefSitesByPC(pc), state) + state.iHandler.finalizeDefSite(allDefSitesByPC(pc)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala similarity index 73% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala index ba58c58d72..118c61cdc8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/GetFieldFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala @@ -10,9 +10,7 @@ package finalizer /** * @author Maximilian Rüsch */ -case class GetFieldFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class FieldReadFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override protected type T = FieldRead[V] @@ -21,7 +19,7 @@ case class GetFieldFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = // Processing the definition site again is enough as the finalization procedure is only // called after all dependencies are resolved. state.iHandler.processDefSite(defSite)(state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala index e78da63ccd..2e7e31a087 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala @@ -18,23 +18,18 @@ package finalizer * result. However, '''this assumes that all partial results are available when finalizing a * result!''' */ -trait L1Finalizer { - - /** - * The computation state to use to retrieve partial results and to write the final result back. - */ - protected val state: L1ComputationState +trait L1Finalizer[State <: L1ComputationState[State]] { 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 + * computed in this function is to be set in [[ComputationState.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 + def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala index 57f5299c7f..156928c88a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala @@ -10,9 +10,7 @@ package finalizer /** * @author Maximilian Rüsch */ -case class NewArrayFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class NewArrayFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override type T = NewArray[V] @@ -21,7 +19,7 @@ case class NewArrayFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = // Simply re-trigger the computation state.iHandler.processDefSite(defSite)(state) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala index 87ee10a2cd..7bfa371711 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala @@ -12,9 +12,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** * @author Maximilian Rüsch */ -case class NonVirtualMethodCallFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class NonVirtualMethodCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override type T = NonVirtualMethodCall[V] @@ -23,12 +21,12 @@ case class NonVirtualMethodCallFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { val toAppend = if (instr.params.nonEmpty) { val defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap defSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(defSitesByPC(pc), state) + state.iHandler.finalizeDefSite(defSitesByPC(pc)) } } StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala index 5373f4fdaf..dedd5c4c39 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala @@ -12,9 +12,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation /** * @author Maximilian Rüsch */ -case class StaticFunctionCallFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class StaticFunctionCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override type T = StaticFunctionCall[V] @@ -23,7 +21,7 @@ case class StaticFunctionCallFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): 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 @@ -33,7 +31,7 @@ case class StaticFunctionCallFinalizer( val defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap defSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(defSitesByPC(pc), state) + state.iHandler.finalizeDefSite(defSitesByPC(pc)) } } StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala index 9c4d1e3262..b21740f241 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala @@ -14,9 +14,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class VirtualFunctionCallFinalizer( - override protected val state: L1ComputationState -) extends L1Finalizer { +case class VirtualFunctionCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { override type T = VirtualFunctionCall[V] @@ -26,7 +24,7 @@ case class VirtualFunctionCallFinalizer( *

      * @inheritdoc */ - override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { instr.name match { case "append" => finalizeAppend(instr, defSite) case "toString" => finalizeToString(instr, defSite) @@ -40,12 +38,12 @@ case class VirtualFunctionCallFinalizer( * interpretation function of * [[org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1VirtualFunctionCallInterpreter]]. */ - private def finalizeAppend(instr: T, defSite: Int): Unit = { + private def finalizeAppend(instr: T, defSite: Int)(implicit state: State): Unit = { val receiverDefSitesByPC = instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap receiverDefSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(receiverDefSitesByPC(pc), state) + state.iHandler.finalizeDefSite(receiverDefSitesByPC(pc)) } } val receiverSci = StringConstancyInformation.reduceMultiple( @@ -63,7 +61,7 @@ case class VirtualFunctionCallFinalizer( instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap paramDefSitesByPC.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(paramDefSitesByPC(pc), state) + state.iHandler.finalizeDefSite(paramDefSitesByPC(pc)) } } val appendSci = if (paramDefSitesByPC.keys.forall(state.fpe2sci.contains)) { @@ -87,11 +85,11 @@ case class VirtualFunctionCallFinalizer( state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci, reset = true) } - private def finalizeToString(instr: T, defSite: Int): Unit = { + private def finalizeToString(instr: T, defSite: Int)(implicit state: State): Unit = { val dependeeSites = instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap dependeeSites.keys.foreach { pc => if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(dependeeSites(pc), state) + state.iHandler.finalizeDefSite(dependeeSites(pc)) } } val finalSci = StringConstancyInformation.reduceMultiple( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index f270598814..69d64dba9a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -32,7 +32,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * * @author Maximilian Rüsch */ -case class L1FieldReadInterpreter[State <: ComputationState[State]]( +case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( ps: PropertyStore, fieldAccessInformation: FieldAccessInformation, project: SomeProject, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 31a75aa03c..7ab8c5e4c5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -28,7 +28,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.GetFieldFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.FieldReadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NonVirtualMethodCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.StaticFunctionCallFinalizer @@ -43,13 +43,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionC * * @author Patrick Mell */ -class L1InterpretationHandler( - ps: PropertyStore, - project: SomeProject, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - contextProvider: ContextProvider -) extends InterpretationHandler[L1ComputationState] { +class L1InterpretationHandler[State <: L1ComputationState[State]]( + project: SomeProject, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider +) extends InterpretationHandler[State] { /** * Processed the given definition site in an interprocedural fashion. @@ -58,7 +58,7 @@ class L1InterpretationHandler( * @inheritdoc */ override def processDefSite(defSite: Int)(implicit - state: L1ComputationState + state: State ): 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" @@ -120,7 +120,7 @@ class L1InterpretationHandler( private def processConstExpr( constExpr: SimpleValueConst, defSite: Int - )(implicit state: L1ComputationState): FinalEP[Entity, StringConstancyProperty] = { + )(implicit state: State): FinalEP[Entity, StringConstancyProperty] = { val finalEP = constExpr match { case ic: IntConst => IntegerValueInterpreter.interpret(ic) case fc: FloatConst => FloatValueInterpreter.interpret(fc) @@ -141,7 +141,7 @@ class L1InterpretationHandler( private def processArrayLoad( expr: ArrayLoad[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = new L0ArrayAccessInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation @@ -159,7 +159,7 @@ class L1InterpretationHandler( private def processNewArray( expr: NewArray[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = new L1NewArrayInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { r.asFinal.p.stringConstancyInformation @@ -175,7 +175,7 @@ class L1InterpretationHandler( * Helper / utility function for processing [[New]] expressions. */ private def processNew(expr: New, defSite: Int)(implicit - state: L1ComputationState + state: State ): FinalEP[Entity, StringConstancyProperty] = { val finalEP = NewInterpreter.interpret(expr) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) @@ -189,7 +189,7 @@ class L1InterpretationHandler( private def processVFC( expr: VirtualFunctionCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).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 @@ -228,7 +228,7 @@ class L1InterpretationHandler( private def processStaticFunctionCall( expr: StaticFunctionCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = new L1StaticFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -242,7 +242,7 @@ class L1InterpretationHandler( * Helper / utility function for processing [[BinaryExpr]]s. */ private def processBinaryExpr(expr: BinaryExpr[V], defSite: Int)(implicit - state: L1ComputationState + state: State ): FinalEP[Entity, StringConstancyProperty] = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions val result = BinaryExprInterpreter.interpret(expr) @@ -256,7 +256,7 @@ class L1InterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField(expr: FieldRead[V], defSite: Int)(implicit - state: L1ComputationState + state: State ): EOptionP[Entity, StringConstancyProperty] = { val r = L1FieldReadInterpreter(ps, fieldAccessInformation, project, declaredFields, contextProvider) .interpret(expr, defSite)(state) @@ -273,8 +273,8 @@ class L1InterpretationHandler( private def processNonVirtualFunctionCall( expr: NonVirtualFunctionCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val r = L1NonVirtualFunctionCallInterpreter(ps, contextProvider).interpret(expr, defSite) + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + val r = L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -288,7 +288,7 @@ class L1InterpretationHandler( def processVirtualMethodCall( expr: VirtualMethodCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = L1VirtualMethodCallInterpreter().interpret(expr, defSite)(state) doInterimResultHandling(r, defSite) r @@ -300,7 +300,7 @@ class L1InterpretationHandler( private def processNonVirtualMethodCall( nvmc: NonVirtualMethodCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { val r = L1NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => @@ -321,7 +321,7 @@ class L1InterpretationHandler( private def doInterimResultHandling( result: EOptionP[Entity, Property], defSite: Int - )(implicit state: L1ComputationState): Unit = { + )(implicit state: State): Unit = { val sci = if (result.isFinal) { result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } else { @@ -333,7 +333,7 @@ class L1InterpretationHandler( /** * Finalized a given definition state. */ - def finalizeDefSite(defSite: Int, state: L1ComputationState): Unit = { + override def finalizeDefSite(defSite: Int)(implicit state: State): Unit = { if (defSite < 0) { state.appendToFpe2Sci( pcOfDefSite(defSite)(state.tac.stmts), @@ -343,23 +343,23 @@ class L1InterpretationHandler( } else { state.tac.stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] => - NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) + NonVirtualMethodCallFinalizer().finalizeInterpretation(nvmc, defSite)(state) case Assignment(_, _, al: ArrayLoad[V]) => - ArrayLoadFinalizer(state).finalizeInterpretation(al, defSite) + ArrayLoadFinalizer().finalizeInterpretation(al, defSite)(state) case Assignment(_, _, na: NewArray[V]) => - NewArrayFinalizer(state).finalizeInterpretation(na, defSite) + NewArrayFinalizer().finalizeInterpretation(na, defSite)(state) case Assignment(_, _, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer(state).finalizeInterpretation(vfc, defSite) + VirtualFunctionCallFinalizer().finalizeInterpretation(vfc, defSite)(state) case ExprStmt(_, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer(state).finalizeInterpretation(vfc, defSite) + VirtualFunctionCallFinalizer().finalizeInterpretation(vfc, defSite)(state) case Assignment(_, _, fr: FieldRead[V]) => - GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) + FieldReadFinalizer().finalizeInterpretation(fr, defSite)(state) case ExprStmt(_, fr: FieldRead[V]) => - GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) + FieldReadFinalizer().finalizeInterpretation(fr, defSite)(state) case Assignment(_, _, sfc: StaticFunctionCall[V]) => - StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) + StaticFunctionCallFinalizer().finalizeInterpretation(sfc, defSite)(state) case ExprStmt(_, sfc: StaticFunctionCall[V]) => - StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) + StaticFunctionCallFinalizer().finalizeInterpretation(sfc, defSite)(state) case _ => state.appendToFpe2Sci( pcOfDefSite(defSite)(state.tac.stmts), @@ -373,17 +373,17 @@ class L1InterpretationHandler( object L1InterpretationHandler { - def apply( - ps: PropertyStore, + def apply[State <: L1ComputationState[State]]( project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, + ps: PropertyStore, contextProvider: ContextProvider - ): L1InterpretationHandler = new L1InterpretationHandler( - ps, + ): L1InterpretationHandler[State] = new L1InterpretationHandler[State]( project, declaredFields, fieldAccessInformation, + ps, contextProvider ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 771979a744..60d0b797c4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -24,7 +24,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -class L1NewArrayInterpreter[State <: ComputationState[State]]( +class L1NewArrayInterpreter[State <: L1ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index f62fff7e1b..625ba38af7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -20,17 +20,17 @@ import org.opalj.fpcf.PropertyStore * * @author Maximilian Rüsch */ -case class L1NonVirtualFunctionCallInterpreter( - ps: PropertyStore, - contextProvider: ContextProvider -) extends L1StringInterpreter[L1ComputationState] { +case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State]]()( + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider +) extends L1StringInterpreter[State] { override type T = NonVirtualFunctionCall[V] override def interpret(instr: T, defSite: Int)(implicit - state: L1ComputationState + state: State ): EOptionP[Entity, StringConstancyProperty] = { - val methods = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) + val methods = getMethodsForPC(instr.pc) if (methods._1.isEmpty) { // No methods available => Return lower bound return FinalEP(instr, StringConstancyProperty.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 0705731e55..1c99e5e949 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -case class L1NonVirtualMethodCallInterpreter[State <: ComputationState[State]]( +case class L1NonVirtualMethodCallInterpreter[State <: L1ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 00ddddbdd8..bc7f3fc2da 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -25,16 +25,16 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -class L1StaticFunctionCallInterpreter( - exprHandler: InterpretationHandler[L1ComputationState], - ps: PropertyStore, - contextProvider: ContextProvider -) extends L1StringInterpreter[L1ComputationState] { +class L1StaticFunctionCallInterpreter[State <: L1ComputationState[State]]( + exprHandler: InterpretationHandler[State], + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider +) extends L1StringInterpreter[State] { override type T = StaticFunctionCall[V] override def interpret(instr: T, defSite: Int)( - implicit state: L1ComputationState + implicit state: State ): EOptionP[Entity, StringConstancyProperty] = { if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { processStringValueOf(instr) @@ -52,7 +52,7 @@ class L1StaticFunctionCallInterpreter( * the parameter passed to the call. */ private def processStringValueOf(call: StaticFunctionCall[V])( - implicit state: L1ComputationState + implicit state: State ): EOptionP[Entity, StringConstancyProperty] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_) } val interim = results.find(_.isRefinable) @@ -79,8 +79,8 @@ class L1StaticFunctionCallInterpreter( private def processArbitraryCall( instr: StaticFunctionCall[V], defSite: Int - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) + )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + val methods, _ = getMethodsForPC(instr.pc) // Static methods cannot be overwritten, thus // 1) we do not need the second return value of getMethodsForPC and diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index d819a5b0a4..8173bd5be9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -12,9 +12,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.PropertyStore @@ -23,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpre /** * @author Maximilian Rüsch */ -trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { +trait L1StringInterpreter[State <: L1ComputationState[State]] extends StringInterpreter[State] { /** * @param instr The instruction that is to be interpreted. It is the responsibility of implementations to make sure @@ -48,16 +46,16 @@ trait L1StringInterpreter[State <: ComputationState[State]] extends StringInterp * second return value indicates whether at least one method has an unknown body (if `true`, * then there is such a method). */ - protected def getMethodsForPC(context: Context, pc: Int)( + protected def getMethodsForPC(pc: Int)( implicit + state: State, ps: PropertyStore, - callees: Callees, contextProvider: ContextProvider ): (List[Method], Boolean) = { var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() - callees.callees(context, pc)(ps, contextProvider).map(_.method).foreach { + state.callees.callees(state.methodContext, pc)(ps, contextProvider).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => hasMethodWithUnknownBody = true } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index ab29a38639..93b394a07a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -30,12 +30,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFu * * @author Patrick Mell */ -class L1VirtualFunctionCallInterpreter( - exprHandler: InterpretationHandler[L1ComputationState], +class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( + exprHandler: InterpretationHandler[State], implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider -) extends L0VirtualFunctionCallInterpreter[L1ComputationState](exprHandler) - with L1StringInterpreter[L1ComputationState] { +) extends L0VirtualFunctionCallInterpreter[State](exprHandler) + with L1StringInterpreter[State] { override type T = VirtualFunctionCall[V] @@ -66,7 +66,7 @@ class L1VirtualFunctionCallInterpreter( */ override protected def handleInterpretation(instr: T, defSite: Int)(implicit - state: L1ComputationState + state: State ): Option[StringConstancyInformation] = { instr.name match { case "append" => interpretAppendCall(instr) @@ -88,9 +88,9 @@ class L1VirtualFunctionCallInterpreter( * finalized later on. */ private def interpretArbitraryCall(instr: T, defSite: Int)( - implicit state: L1ComputationState + implicit state: State ): Option[StringConstancyInformation] = { - val (methods, _) = getMethodsForPC(state.methodContext, instr.pc)(ps, state.callees, contextProvider) + val (methods, _) = getMethodsForPC(instr.pc) if (methods.isEmpty) { return Some(StringConstancyInformation.lb) @@ -174,7 +174,7 @@ class L1VirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall(appendCall: VirtualFunctionCall[V])( - implicit state: L1ComputationState + implicit state: State ): Option[StringConstancyInformation] = { val receiverResults = receiverValuesOfAppendCall(appendCall) val appendResult = valueOfAppendCall(appendCall) @@ -230,7 +230,7 @@ class L1VirtualFunctionCallInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] - )(implicit state: L1ComputationState): List[EOptionP[Entity, StringConstancyProperty]] = { + )(implicit state: State): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) @@ -260,7 +260,7 @@ class L1VirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V] - )(implicit state: L1ComputationState): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): 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 diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 6768bf0966..4035148ece 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -19,7 +19,7 @@ import org.opalj.fpcf.FinalEP * * @author Patrick Mell */ -case class L1VirtualMethodCallInterpreter[State <: ComputationState[State]]() extends L1StringInterpreter[State] { +case class L1VirtualMethodCallInterpreter[State <: L1ComputationState[State]]() extends L1StringInterpreter[State] { override type T = VirtualMethodCall[V] From 4f57fc178acd0d7e3bf63795b88f238ce82112c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 16:37:14 +0100 Subject: [PATCH 363/583] Fix field interpretation for l1 tests --- .../org/opalj/fpcf/StringAnalysisTest.scala | 7 +++ .../L1FieldReadInterpreter.scala | 52 ++++++++----------- 2 files changed, 28 insertions(+), 31 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 27e79df0a6..9d1d1f1844 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 @@ -10,6 +10,7 @@ import org.opalj.br.Annotation import org.opalj.br.Annotations import org.opalj.br.Method import org.opalj.br.ObjectType +import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey @@ -18,6 +19,7 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.SEntity import org.opalj.tac.fpcf.analyses.string_analysis.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis @@ -188,6 +190,11 @@ class L1StringAnalysisTest extends StringAnalysisTest { override def level = 1 override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData(FieldAccessInformationKey) { + case None => Seq(EagerFieldAccessInformationAnalysis) + case Some(requirements) => requirements :+ EagerFieldAccessInformationAnalysis + } + p.get(RTACallGraphKey) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 69d64dba9a..92ac193a5c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -43,12 +43,8 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( override type T = FieldRead[V] /** - * 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''. + * 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. */ private val fieldWriteThreshold = { val threshold = @@ -72,25 +68,21 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( } /** - * Currently, fields are approximated using the following approach. If a field of a type not - * supported by the [[L1StringAnalysis]] 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]]. + * Currently, fields are approximated using the following approach: If a field of a type not supported by the + * [[L1StringAnalysis]] 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]]. */ override def interpret(instr: T, defSite: Int)(implicit state: State): 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 + // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { return FinalEP(instr, StringConstancyProperty.lb) } - // Write accesses exceeds the threshold => approximate with lower bound + val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField - val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField) + val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { return FinalEP(instr, StringConstancyProperty.lb) } @@ -107,7 +99,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( val (tacEps, tac) = getTACAI(ps, method, state) val nextResult = if (parameter.isEmpty) { // Field parameter information is not available - FinalEP(defSitEntity, StringConstancyProperty.lb) + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) } else if (tacEps.isRefinable) { EPK(state.entity, StringConstancyProperty.key) } else { @@ -127,28 +119,26 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( eps case _ => // No TAC available - FinalEP(defSitEntity, StringConstancyProperty.lb) + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) } } results.append(nextResult) } if (results.isEmpty) { - // No methods, which write the field, were found => Field could either be null or - // any value + // No methods which write the field were found => Field could either be null or any value val sci = StringConstancyInformation( StringConstancyLevel.DYNAMIC, - possibleStrings = "(^null$|" + StringConstancyInformation.UnknownWordSymbol + ")" + possibleStrings = + s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" ) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - FinalEP(defSitEntity, StringConstancyProperty(sci)) + FinalEP(defSite.asInstanceOf[Integer], 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(_.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 + // 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(FinalEP( instr, @@ -159,7 +149,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( _.asFinal.p.stringConstancyInformation }) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) - FinalEP(defSitEntity, StringConstancyProperty(finalSci)) + FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(finalSci)) } else { results.find(!_.isFinal).get } From eb304c62c7aaad2da2e832aa81b04321ead42739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 16:39:42 +0100 Subject: [PATCH 364/583] Remove unused l0 interpreters --- .../L0GetFieldInterpreter.scala | 30 ----------------- .../L0GetStaticInterpreter.scala | 32 ------------------- .../L0InterpretationHandler.scala | 3 +- 3 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala deleted file mode 100644 index c0d44a0dc8..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetFieldInterpreter.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l0 -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP - -/** - * Responsible for processing [[GetField]]s. Currently, there is no support for fields, i.e., they are not analyzed but - * a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned. - * - * @author Maximilian Rüsch - */ -case class L0GetFieldInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { - - override type T = GetField[V] - - /** - * Fields are currently unsupported, thus this function always returns [[StringConstancyProperty.lb]]. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty.lb) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala deleted file mode 100644 index 23a4a570cc..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0GetStaticInterpreter.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l0 -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP - -/** - * The `IntraproceduralGetStaticInterpreter` is responsible for processing - * [[org.opalj.tac.GetStatic]]s in an intraprocedural fashion. Thus, they are not analyzed but a - * fixed [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned - * (see [[interpret]]). - * - * @author Patrick Mell - */ -case class L0GetStaticInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { - - override type T = GetStatic - - /** - * Currently, this type is not interpreted. Thus, this function always returns [[StringConstancyProperty.lb]]. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = - FinalEP(instr, StringConstancyProperty.lb) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index e862bc077b..34cca40ddd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -85,7 +85,8 @@ class L0InterpretationHandler[State <: L0ComputationState[State]]()( case Assignment(_, _, expr: New) => NewInterpreter.interpret(expr) case Assignment(_, _, expr: GetField[V]) => - L0GetFieldInterpreter().interpret(expr, defSite)(state) + // Currently unsupported + FinalEP(expr, StringConstancyProperty.lb) case Assignment(_, _, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => From 71475f74155e26bcf30410912d7ed57360fa533b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 10 Feb 2024 16:52:34 +0100 Subject: [PATCH 365/583] Repair some l1 test cases --- .../string_analysis/l0/L0TestMethods.java | 8 +- .../string_analysis/l1/L1TestMethods.java | 155 +++++++++++++++--- .../org/opalj/fpcf/StringAnalysisTest.scala | 23 ++- .../L1StaticFunctionCallInterpreter.scala | 2 +- 4 files changed, 149 insertions(+), 39 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 3bdb00bae0..538d8ca0e8 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -826,21 +826,21 @@ public void tryCatchFinallyWithThrowable(String filename) { value = "simple examples to clear a StringBuilder", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" + expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" + expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" ) }) public void simpleClearExamples() { StringBuilder sb1 = new StringBuilder("init_value:"); sb1.setLength(0); - sb1.append(getStringBuilderClassName()); + sb1.append("java.lang.StringBuilder"); StringBuilder sb2 = new StringBuilder("init_value:"); System.out.println(sb2.toString()); sb2 = new StringBuilder(); - sb2.append(getStringBuilderClassName()); + sb2.append("java.lang.StringBuilder"); analyzeString(sb1.toString()); analyzeString(sb2.toString()); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 9e88f2ece5..f18afa2346 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -11,7 +11,10 @@ import javax.management.remote.rmi.RMIServer; import java.io.File; import java.io.FileNotFoundException; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Random; import java.util.Scanner; import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; @@ -97,18 +100,6 @@ 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|Object|Runtime)" - ) - }) - 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 be interpret", @@ -144,20 +135,18 @@ public void methodOutOfScopeTest() throws FileNotFoundException { } @StringDefinitionsCollection( - value = "a case where an array access needs to be interpreted interprocedurally", + value = "a case where an array access needs to be interpreted with a virtual call, requiring a call graph", stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|.*|java.lang.(Integer|" - + "Object|Runtime)|.*)" + expectedStrings = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)" ) - }) public void arrayTest(int i) { String[] classes = { "java.lang.Object", getRuntimeClassName(), - StringProvider.getFQClassName("java.lang", "Integer"), + StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), System.getProperty("SomeClass") }; analyzeString(classes[i]); @@ -256,14 +245,14 @@ public void unknownHierarchyInstanceTest(GreetingService greetingService) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "java.lang.(Integer|Object|Runtime)" + expectedStrings = "java.lang.(Object|Runtime)" ) }) public void contextInsensitivityTest() { StringBuilder sb = new StringBuilder(); - String s = StringProvider.getFQClassName("java.lang", "Object"); - sb.append(StringProvider.getFQClassName("java.lang", "Runtime")); + String s = StringProvider.getFQClassNameWithStringBuilder("java.lang", "Object"); + sb.append(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Runtime")); analyzeString(sb.toString()); } @@ -404,13 +393,11 @@ public void noCallersInformationRequiredTest(String s) { } @StringDefinitionsCollection( - value = "a case taken from com.sun.prism.impl.ps.BaseShaderContext#getPaintShader " - + "and slightly adapted", + 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)?" + expectedStrings = "Hello, World_paintname((_PAD|_REFLECT|_REPEAT)?)?(_AlphaTest)?" ) }) public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { @@ -442,7 +429,7 @@ private static String tieNameForCompiler(String var0) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(some value|another value|^null$)" + expectedStrings = "(another value|some value|^null$)" ) }) public void fieldReadTest() { @@ -560,7 +547,7 @@ private String severalReturnValuesFunction(String s, int i) { } @StringDefinitionsCollection( - value = "a case where a non static function has multiple return values", + value = "a case where a static function has multiple return values", stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, @@ -673,6 +660,122 @@ public void getStringArrayField(int i) { analyzeString(monthNames[i]); } + // DIFFERING TEST CASES FROM PREVIOUS LEVELS + + @StringDefinitionsCollection( + value = "can handle virtual function calls", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" + ) + }) + public void fromFunctionCall() { + String className = getStringBuilderClassName(); + analyzeString(className); + } + + @StringDefinitionsCollection( + // IMPROVE subsequent levels should not worsen quality of results + value = "checks if a string value with append(s) is determined correctly", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(java.lang.|lang.|java.)(String|Object|(java.lang.|lang.|java.)(String|Object))" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(java.lang.|lang.|java.)(String|Object|(java.lang.|lang.|java.)(String|Object))" + ) + } + ) + public void simpleStringConcatWithStaticFunctionCalls() { + analyzeString(StringProvider.concat("java.lang.", "String")); + analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); + } + + @StringDefinitionsCollection( + value = "Some comprehensive example for experimental purposes taken from the JDK and slightly modified", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?" + ), + }) + 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 extensive example with many control structures", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): " + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?" + ) + }) + public void extensive(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()); + } + + analyzeString(sb.toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } 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 9d1d1f1844..adee63a297 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 @@ -164,10 +164,10 @@ class L0StringAnalysisTest extends StringAnalysisTest { // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) - .filterNot(entity => entity._2.name == "simpleStringConcat") - .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) - .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) + .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) @@ -204,10 +204,17 @@ class L1StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) - .filterNot(entity => entity._2.name == "simpleStringConcat") - .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) - .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) + // L0 Tests + .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" + // L1 Tests + .filterNot(entity => entity._2.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("dependenciesWithinFinalizeTest")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("getPaintShader")) // Waits on string_concat and "substring" + .filterNot(entity => entity._2.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.shutdown() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index bc7f3fc2da..1c154bc0dd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -99,7 +99,7 @@ class L1StaticFunctionCallInterpreter[State <: L1ComputationState[State]]( calledMethods.exists(m => m.method.name == instr.name && m.method.declaringClassType == instr.declaringClass ) - }.keys + }.keys.toList.sorted // 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 From e688f937c9ab7a933250a7d7b4b9eddf6614e497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 21:40:28 +0100 Subject: [PATCH 366/583] Properly deduplicate possible string values in string constancy --- .../string_analysis/l0/L0TestMethods.java | 5 ++--- .../StringConstancyInformation.scala | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 538d8ca0e8..dba4664180 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -195,7 +195,7 @@ public void fromStringArray(int index) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|.*|java.lang.Integer|.*)" + expectedStrings = "(java.lang.Object|.*|java.lang.Integer)" ) }) @@ -233,8 +233,7 @@ public void multipleConstantDefSites(boolean cond) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "((java.lang.Object|.*)|java.lang.System|" - + "java.lang..*|.*)" + expectedStrings = "((java.lang.Object|.*)|.*|java.lang.System|java.lang..*)" ) }) public void multipleDefSites(int value) { 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 6c732ec542..a0e7daf99b 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 @@ -29,6 +29,16 @@ case class StringConstancyInformation( constancyType == StringConstancyType.APPEND && possibleStrings == "" + /** + * Checks whether the instance is a complex element in the sense that it is either non-constant or contains some + * characters that make finding substrings harder. + */ + def isComplex: Boolean = + constancyLevel != StringConstancyLevel.CONSTANT || + possibleStrings.contains("|") || + possibleStrings.contains("?") || + possibleStrings.contains("(") || + possibleStrings.contains(")") } /** @@ -81,8 +91,16 @@ object StringConstancyInformation { // the neutral element case 0 => StringConstancyInformation.getNeutralElement case 1 => relScis.head - case _ => // Reduce - val reduced = relScis.reduceLeft((o, n) => + case _ => // Deduplicate and reduce + var seenStrings = Set.empty[String] + val reduced = relScis.flatMap { sci => + if (seenStrings.contains(sci.possibleStrings)) { + None + } else { + seenStrings += sci.possibleStrings + Some(sci) + } + } reduceLeft((o, n) => StringConstancyInformation( StringConstancyLevel.determineMoreGeneral( o.constancyLevel, From 703aa8abddcd356f76d7453fe0e912013ddfa462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 21:42:29 +0100 Subject: [PATCH 367/583] Support substring calls on l0 string analysis --- .../string_analysis/l0/L0TestMethods.java | 17 ++++ .../org/opalj/fpcf/StringAnalysisTest.scala | 4 - .../L0VirtualFunctionCallInterpreter.scala | 80 ++++++++++++++----- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index dba4664180..b6bc12ac52 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -117,6 +117,23 @@ public void simpleStringConcat() { analyzeString(className2); } + @StringDefinitionsCollection( + value = "checks if the substring of a constant string value is determined correctly", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "va" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "va.lang." + ) + } + ) + public void simpleSubstring() { + String someString = "java.lang."; + analyzeString(someString.substring(2, 5)); + analyzeString(someString.substring(2)); + } + @StringDefinitionsCollection( value = "checks if a string value with append(s) is determined correctly", stringDefinitions = { 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 adee63a297..5a3aad155f 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 @@ -164,10 +164,6 @@ class L0StringAnalysisTest extends StringAnalysisTest { // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 9eecfe89a4..2ddb5b2ecb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -22,6 +22,7 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.value.TheIntegerValue /** * Responsible for processing [[VirtualFunctionCall]]s without a call graph. @@ -76,6 +77,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) case "replace" => Some(interpretReplaceCall) + case "substring" if instr.descriptor.returnType == ObjectType.String => interpretSubstringCall(instr) case _ => instr.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => Some(StringConstancyInformation.lb) @@ -97,7 +99,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( private def interpretAppendCall(appendCall: VirtualFunctionCall[V])(implicit state: State ): Option[StringConstancyInformation] = { - val receiverSci = receiverValuesOfAppendCall(appendCall) + val receiverSci = receiverValuesOfCall(appendCall) val appendSci = valueOfAppendCall(appendCall) if (appendSci.isEmpty) { @@ -128,21 +130,6 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( } } - /** - * This function determines the current value of the receiver object of an `append` call. - */ - private def receiverValuesOfAppendCall(call: VirtualFunctionCall[V])(implicit - state: State - ): 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 - val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => - val r = exprHandler.processDefSite(ds) - r.asFinal.p.stringConstancyInformation - }.filter { sci => !sci.isTheNeutralElement } - scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement) - } - /** * 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. @@ -191,13 +178,68 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( } } + /** + * Processes calls to [[String#substring]]. + */ + private def interpretSubstringCall(substringCall: T)(implicit state: State): Option[StringConstancyInformation] = { + val receiverSci = receiverValuesOfCall(substringCall) + + if (receiverSci.isComplex) { + // We cannot yet interpret substrings of mixed values + Some(StringConstancyInformation.lb) + } else { + val parameterCount = substringCall.params.size + parameterCount match { + case 1 => + substringCall.params.head.asVar.value match { + case intValue: TheIntegerValue => + Some(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.REPLACE, + receiverSci.possibleStrings.substring(intValue.value) + )) + case _ => + Some(StringConstancyInformation.lb) + } + + case 2 => + (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { + case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => + Some(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) + )) + case _ => + Some(StringConstancyInformation.lb) + } + + case _ => throw new IllegalStateException( + s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" + ) + } + } + } + + /** + * This function determines the current value of the receiver object of a call. + */ + private def receiverValuesOfCall(call: T)(implicit state: State): 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 + val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => + // IMPROVE enable handling dependees here + val r = exprHandler.processDefSite(ds) + r.asFinal.p.stringConstancyInformation + }.filter { sci => !sci.isTheNeutralElement } + scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement) + } + /** * Processes 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])(implicit - state: State - ): Option[StringConstancyInformation] = { + private def interpretToStringCall(call: T)(implicit state: State): Option[StringConstancyInformation] = { handleInterpretationResult(exprHandler.processDefSite(call.receiver.asVar.definedBy.head)) } From d4adea8b95e5929ca9243ee7c564c5dd21625fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 21:58:16 +0100 Subject: [PATCH 368/583] Fix formatting after scalaFmt intro post work --- .../fpcf/properties/StringConstancyProperty.scala | 2 +- .../StringConstancyInformation.scala | 8 ++++---- .../properties/string_definition/StringTree.scala | 14 +++++++------- OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala | 4 ++-- .../string_analysis/l0/L0StringAnalysis.scala | 4 ++-- .../interpretation/L0ArrayAccessInterpreter.scala | 2 +- .../interpretation/L0InterpretationHandler.scala | 6 +++--- .../L0NonVirtualMethodCallInterpreter.scala | 2 +- .../L0StaticFunctionCallInterpreter.scala | 8 ++++---- .../L0VirtualFunctionCallInterpreter.scala | 12 ++++++------ .../string_analysis/l1/L1StringAnalysis.scala | 6 +++--- .../l1/interpretation/L1FieldReadInterpreter.scala | 10 +++++----- .../interpretation/L1InterpretationHandler.scala | 10 +++++----- .../l1/interpretation/L1NewArrayInterpreter.scala | 2 +- .../L1NonVirtualFunctionCallInterpreter.scala | 4 ++-- .../L1NonVirtualMethodCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 6 +++--- .../L1VirtualFunctionCallInterpreter.scala | 6 +++--- .../string_analysis/preprocessing/Path.scala | 4 ++-- 19 files changed, 56 insertions(+), 56 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 d2e8a36b7b..4d30259835 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 @@ -19,7 +19,7 @@ sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformat } class StringConstancyProperty( - val stringConstancyInformation: StringConstancyInformation + val stringConstancyInformation: StringConstancyInformation ) extends Property with StringConstancyPropertyMetaInformation { final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key 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 a0e7daf99b..351a6c4a4a 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 @@ -12,9 +12,9 @@ package string_definition * @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 = "" ) { /** @@ -100,7 +100,7 @@ object StringConstancyInformation { seenStrings += sci.possibleStrings Some(sci) } - } reduceLeft((o, n) => + } reduceLeft ((o, n) => StringConstancyInformation( StringConstancyLevel.determineMoreGeneral( o.constancyLevel, 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 d0cd6cb639..d887ee10dc 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 @@ -322,9 +322,9 @@ sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // ques * Otherwise, the number of repetitions is computed by `upperBound - lowerBound`. */ case class StringTreeRepetition( - child: StringTree, - lowerBound: Option[Int] = None, - upperBound: Option[Int] = None + child: StringTree, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None ) extends StringTree(ListBuffer(child)) /** @@ -334,7 +334,7 @@ case class StringTreeRepetition( * represents ''s_1'' and the last child / last element ''s_n''. */ case class StringTreeConcat( - override val children: ListBuffer[StringTree] + override val children: ListBuffer[StringTree] ) extends StringTree(children) /** @@ -347,7 +347,7 @@ case class StringTreeConcat( * a (sub) string. */ case class StringTreeOr( - override val children: ListBuffer[StringTree] + override val children: ListBuffer[StringTree] ) extends StringTree(children) /** @@ -359,7 +359,7 @@ case class StringTreeOr( * string may have (contain) a particular but not necessarily. */ case class StringTreeCond( - child: StringTree + child: StringTree ) extends StringTree(ListBuffer(child)) /** @@ -370,5 +370,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 StringTree(ListBuffer()) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala index efd60520a3..6a140cb6ff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala @@ -36,8 +36,8 @@ abstract class PDUVar[+Value <: ValueInformation] { } class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( - val value: Value, - val defPCs: PCs + val value: Value, + val defPCs: PCs ) extends PDUVar[Value] { def usePCs: Nothing = throw new UnsupportedOperationException diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 7c7bf4e02f..cf5defcd9f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -59,8 +59,8 @@ trait L0ComputationState[State <: L0ComputationState[State]] extends Computation class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { protected[l0] case class CState( - override val dm: DeclaredMethod, - override val entity: SContext + override val dm: DeclaredMethod, + override val entity: SContext ) extends L0ComputationState[CState] override type State = CState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 999e00c086..2c871ff89f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -22,7 +22,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { implicit val _exprHandler: InterpretationHandler[State] = exprHandler diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 34cca40ddd..7f3d8d9957 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -34,9 +34,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt * @author Maximilian Rüsch */ class L0InterpretationHandler[State <: L0ComputationState[State]]()( - implicit - p: SomeProject, - ps: PropertyStore + implicit + p: SomeProject, + ps: PropertyStore ) extends InterpretationHandler[State] { /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 3f330cf4f9..ca1d5c5a0e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -22,7 +22,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { implicit val _exprHandler: InterpretationHandler[State] = exprHandler diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index c7cba67307..e8024e237f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -23,11 +23,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] )( - implicit - p: SomeProject, - ps: PropertyStore + implicit + p: SomeProject, + ps: PropertyStore ) extends L0StringInterpreter[State] { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 2ddb5b2ecb..d341a22c18 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -31,7 +31,7 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { implicit val _exprHandler: InterpretationHandler[State] = exprHandler @@ -74,9 +74,9 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( state: State ): Option[StringConstancyInformation] = { instr.name match { - case "append" => interpretAppendCall(instr) - case "toString" => interpretToStringCall(instr) - case "replace" => Some(interpretReplaceCall) + case "append" => interpretAppendCall(instr) + case "toString" => interpretToStringCall(instr) + case "replace" => Some(interpretReplaceCall) case "substring" if instr.descriptor.returnType == ObjectType.String => interpretSubstringCall(instr) case _ => instr.descriptor.returnType match { @@ -215,8 +215,8 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( } case _ => throw new IllegalStateException( - s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" - ) + s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" + ) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index ac14f67213..aa8ba752ab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -75,9 +75,9 @@ import org.opalj.tac.fpcf.properties.TACAI class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { protected[l1] case class CState( - override val dm: DeclaredMethod, - override val entity: (SEntity, Method), - override val methodContext: Context + override val dm: DeclaredMethod, + override val entity: (SEntity, Method), + override val methodContext: Context ) extends L1ComputationState[CState] override type State = CState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 92ac193a5c..68f98ad27f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -33,11 +33,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * @author Maximilian Rüsch */ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( - ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, - project: SomeProject, - implicit val declaredFields: DeclaredFields, - implicit val contextProvider: ContextProvider + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + project: SomeProject, + implicit val declaredFields: DeclaredFields, + implicit val contextProvider: ContextProvider ) extends L1StringInterpreter[State] { override type T = FieldRead[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 7ab8c5e4c5..1f0b9ea1c1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -44,11 +44,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionC * @author Patrick Mell */ class L1InterpretationHandler[State <: L1ComputationState[State]]( - project: SomeProject, - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + project: SomeProject, + declaredFields: DeclaredFields, + fieldAccessInformation: FieldAccessInformation, + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider ) extends InterpretationHandler[State] { /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 60d0b797c4..0764ccf69e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -25,7 +25,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { override type T = NewArray[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 625ba38af7..23e6935cea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -21,8 +21,8 @@ import org.opalj.fpcf.PropertyStore * @author Maximilian Rüsch */ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State]]()( - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider ) extends L1StringInterpreter[State] { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 1c99e5e949..fd696cc20e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Patrick Mell */ case class L1NonVirtualMethodCallInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State] + exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 1c154bc0dd..3cbed7fb3b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -26,9 +26,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * @author Maximilian Rüsch */ class L1StaticFunctionCallInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State], - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + exprHandler: InterpretationHandler[State], + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider ) extends L1StringInterpreter[State] { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 93b394a07a..2179b5fdb0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -31,9 +31,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFu * @author Patrick Mell */ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State], - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + exprHandler: InterpretationHandler[State], + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider ) extends L0VirtualFunctionCallInterpreter[State](exprHandler) with L1StringInterpreter[State] { 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 f2ee742003..7588c50733 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 @@ -88,8 +88,8 @@ case object NestedPathType extends Enumeration { * possible, i.e., when they compute / have this information. */ case class NestedPathElement( - element: ListBuffer[SubPath], - elementType: Option[NestedPathType.Value] + element: ListBuffer[SubPath], + elementType: Option[NestedPathType.Value] ) extends SubPath /** From cb124c35795c4afac615028eed6921ae5f7cbfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 22:05:27 +0100 Subject: [PATCH 369/583] Fix substring test --- .../opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index b6bc12ac52..fa4ae4042c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -121,7 +121,7 @@ public void simpleStringConcat() { value = "checks if the substring of a constant string value is determined correctly", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "va" + expectedLevel = CONSTANT, expectedStrings = "va." ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "va.lang." From a1c8f6f43f13fa057e7d7010cc4a03f1c268134a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 22:32:35 +0100 Subject: [PATCH 370/583] Exclude some broken L1 tests from general test runs --- .../string_analysis/l0/L0TestMethods.java | 4 ++-- .../string_analysis/l1/L1TestMethods.java | 18 ------------------ .../org/opalj/fpcf/StringAnalysisTest.scala | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index fa4ae4042c..4afa3af906 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -208,7 +208,7 @@ public void fromStringArray(int index) { } @StringDefinitionsCollection( - value = "a case where an array access needs to be interpreted with multiple static function calls", + value = "a case where an array access needs to be interpreted with multiple static and virtual function calls", stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, @@ -216,7 +216,7 @@ public void fromStringArray(int index) { ) }) - public void arrayStaticFunctionCalls(int i) { + public void arrayStaticAndVirtualFunctionCalls(int i) { String[] classes = { "java.lang.Object", getRuntimeClassName(), diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index f18afa2346..6467c72ee1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -134,24 +134,6 @@ public void methodOutOfScopeTest() throws FileNotFoundException { analyzeString(sb.toString()); } - @StringDefinitionsCollection( - value = "a case where an array access needs to be interpreted with a virtual call, requiring a call graph", - stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)" - ) - }) - public void arrayTest(int i) { - String[] classes = { - "java.lang.Object", - getRuntimeClassName(), - StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), - System.getProperty("SomeClass") - }; - analyzeString(classes[i]); - } - @StringDefinitionsCollection( value = "a case that tests that the append interpretation of only intraprocedural " + "expressions still works", 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 5a3aad155f..3af9860980 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 @@ -211,6 +211,21 @@ class L1StringAnalysisTest extends StringAnalysisTest { .filterNot(entity => entity._2.name.startsWith("dependenciesWithinFinalizeTest")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("getPaintShader")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" + // Currently broken L1 Tests + .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) + .filterNot(entity => entity._2.name.startsWith("unknownHierarchyInstanceTest")) + .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest1")) + .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest2")) + .filterNot(entity => entity._2.name.startsWith("setDebugFlags")) + .filterNot(entity => entity._2.name.startsWith("crissCrossExample")) + .filterNot(entity => entity._2.name.startsWith("breakContinueExamples")) + .filterNot(entity => entity._2.name.startsWith("simpleSecondStringBuilderRead")) + .filterNot(entity => entity._2.name.startsWith("complexSecondStringBuilderRead")) + .filterNot(entity => entity._2.name.startsWith("directAppendConcatsWith2ndStringBuilder")) + .filterNot(entity => entity._2.name.startsWith("parameterRead")) + .filterNot(entity => entity._2.name.startsWith("ifElseWithStringBuilder4")) + .filterNot(entity => entity._2.name.startsWith("ifElseWithStringBuilderWithFloatExpr")) + .filterNot(entity => entity._2.name.startsWith("fromStringArray")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.shutdown() From 61e072be33e90ec83c13ad17a9f505a13caeae97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 22:52:25 +0100 Subject: [PATCH 371/583] Reduce static function call handling to l0 level --- .../string_analysis/l0/L0TestMethods.java | 22 +++ .../string_analysis/l1/L1TestMethods.java | 87 +++------- .../org/opalj/fpcf/StringAnalysisTest.scala | 4 + .../L0StaticFunctionCallInterpreter.scala | 40 +++++ .../L0VirtualFunctionCallInterpreter.scala | 7 +- .../string_analysis/l1/L1StringAnalysis.scala | 4 +- .../L1InterpretationHandler.scala | 11 +- .../L1StaticFunctionCallInterpreter.scala | 160 ------------------ 8 files changed, 101 insertions(+), 234 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 4afa3af906..43633167d1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1042,6 +1042,28 @@ public void simpleSecondStringBuilderRead(String className) { analyzeString(sb1.toString()); } + @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 = DYNAMIC, + expectedStrings = ".*" + ) + }) + public void valueOfTest() { + analyzeString(String.valueOf('c')); + analyzeString(String.valueOf((float) 42.3)); + analyzeString(String.valueOf(getRuntimeClassName())); + } + @StringDefinitionsCollection( value = "an example that uses a non final field", stringDefinitions = { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 6467c72ee1..6ce8bce334 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -222,22 +222,6 @@ 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.(Object|Runtime)" - ) - - }) - public void contextInsensitivityTest() { - StringBuilder sb = new StringBuilder(); - String s = StringProvider.getFQClassNameWithStringBuilder("java.lang", "Object"); - sb.append(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Runtime")); - analyzeString(sb.toString()); - } - @StringDefinitionsCollection( value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " + "is involved", @@ -584,40 +568,6 @@ public static String noReturnFunction2() { throw new RuntimeException(); } - @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())); - } - - @StringDefinitionsCollection( - value = "a test case which tests the interpretation of String#valueOf", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "java.lang.Runtime" - ) - }) - public void valueOfTest2() { - analyzeString(String.valueOf(getRuntimeClassName())); - } - @StringDefinitionsCollection( value = "a case where a static property is read", stringDefinitions = { @@ -645,32 +595,37 @@ public void getStringArrayField(int i) { // DIFFERING TEST CASES FROM PREVIOUS LEVELS @StringDefinitionsCollection( - value = "can handle virtual function calls", + value = "a test case which tests the interpretation of String#valueOf", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" + expectedLevel = CONSTANT, + expectedStrings = "c" + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "42.3" + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.Runtime" ) }) - public void fromFunctionCall() { - String className = getStringBuilderClassName(); - analyzeString(className); + public void valueOfTest() { + analyzeString(String.valueOf('c')); + analyzeString(String.valueOf((float) 42.3)); + analyzeString(String.valueOf(getRuntimeClassName())); } @StringDefinitionsCollection( - // IMPROVE subsequent levels should not worsen quality of results - value = "checks if a string value with append(s) is determined correctly", + value = "can handle virtual function calls", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(java.lang.|lang.|java.)(String|Object|(java.lang.|lang.|java.)(String|Object))" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(java.lang.|lang.|java.)(String|Object|(java.lang.|lang.|java.)(String|Object))" + expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" ) - } - ) - public void simpleStringConcatWithStaticFunctionCalls() { - analyzeString(StringProvider.concat("java.lang.", "String")); - analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); + }) + public void fromFunctionCall() { + String className = getStringBuilderClassName(); + analyzeString(className); } @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 3af9860980..204d74d1de 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 @@ -164,6 +164,8 @@ class L0StringAnalysisTest extends StringAnalysisTest { // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) + // Currently broken L0 Tests + .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) @@ -205,6 +207,8 @@ class L1StringAnalysisTest extends StringAnalysisTest { .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" + // Currently broken L0 Tests + .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) // L1 Tests .filterNot(entity => entity._2.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index e8024e237f..e95b642a71 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -7,6 +7,9 @@ package string_analysis package l0 package interpretation +import scala.util.Try + +import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -33,6 +36,43 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( override type T = StaticFunctionCall[V] override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { + processStringValueOf(instr) + } else { + processArbitraryCall(instr, defSite) + } + } + + private def processStringValueOf(call: StaticFunctionCall[V])( + implicit state: State + ): EOptionP[Entity, StringConstancyProperty] = { + val results = call.params.head.asVar.definedBy.toArray.sorted.map { + exprHandler.processDefSite(_) + } + 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 = results.map { r => r.asFinal.p.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 { + sci + } + } + } else { + scis + } + FinalEP(call, StringConstancyProperty(StringConstancyInformation.reduceMultiple(finalScis))) + } + } + + protected def processArbitraryCall(instr: T, defSite: Int)(implicit + state: State + ): EOptionP[Entity, StringConstancyProperty] = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index d341a22c18..66bacf18eb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -7,6 +7,8 @@ package string_analysis package l0 package interpretation +import scala.util.Try + import org.opalj.br.ComputationalTypeDouble import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -159,7 +161,10 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( if (call.descriptor.parameterType(0).isCharType && sci.constancyLevel == StringConstancyLevel.CONSTANT ) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + if (Try(sci.possibleStrings.toInt).isSuccess) { + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } else + sci } else { sci } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index aa8ba752ab..751fa77a8a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -164,9 +164,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.iHandler == null) { state.iHandler = - L1InterpretationHandler(project, declaredFields, fieldAccessInformation, ps, contextProvider) + L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) state.interimIHandler = - L1InterpretationHandler(project, declaredFields, fieldAccessInformation, ps, contextProvider) + L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) } var requiresCallersInfo = false diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 1f0b9ea1c1..93794e73b2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -27,6 +27,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.FieldReadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer @@ -44,9 +45,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionC * @author Patrick Mell */ class L1InterpretationHandler[State <: L1ComputationState[State]]( - project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, + implicit val p: SomeProject, implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider ) extends InterpretationHandler[State] { @@ -229,7 +230,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( expr: StaticFunctionCall[V], defSite: Int )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val r = new L1StaticFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) + val r = L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -258,7 +259,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processGetField(expr: FieldRead[V], defSite: Int)(implicit state: State ): EOptionP[Entity, StringConstancyProperty] = { - val r = L1FieldReadInterpreter(ps, fieldAccessInformation, project, declaredFields, contextProvider) + val r = L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider) .interpret(expr, defSite)(state) if (r.isRefinable) { processedDefSites.remove(defSite) @@ -374,15 +375,15 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( object L1InterpretationHandler { def apply[State <: L1ComputationState[State]]( - project: SomeProject, declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, + project: SomeProject, ps: PropertyStore, contextProvider: ContextProvider ): L1InterpretationHandler[State] = new L1InterpretationHandler[State]( - project, declaredFields, fieldAccessInformation, + project, ps, contextProvider ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala deleted file mode 100644 index 3cbed7fb3b..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ /dev/null @@ -1,160 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import scala.util.Try - -import org.opalj.br.ObjectType -import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * Responsible for processing [[StaticFunctionCall]]s with a call graph. - * - * @author Maximilian Rüsch - */ -class L1StaticFunctionCallInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State], - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider -) extends L1StringInterpreter[State] { - - override type T = StaticFunctionCall[V] - - override def interpret(instr: T, defSite: Int)( - implicit state: State - ): EOptionP[Entity, StringConstancyProperty] = { - if (instr.declaringClass == ObjectType.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])( - implicit state: State - ): EOptionP[Entity, StringConstancyProperty] = { - val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_) } - 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 = results.map { r => r.asFinal.p.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 { - sci - } - } - } else { - scis - } - FinalEP(call, StringConstancyProperty(StringConstancyInformation.reduceMultiple(finalScis))) - } - } - - private def processArbitraryCall( - instr: StaticFunctionCall[V], - defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val methods, _ = getMethodsForPC(instr.pc) - - // 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(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalEP(instr, StringConstancyProperty.lb) - } - - val m = methods._1.head - val (_, tac) = getTACAI(ps, m, state) - - val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) - val relevantPCs = directCallSites.filter { - case (_, calledMethods) => - calledMethods.exists(m => - m.method.name == instr.name && m.method.declaringClassType == instr.declaringClass - ) - }.keys.toList.sorted - - // 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 params = if (state.nonFinalFunctionArgs.contains(instr)) { - state.nonFinalFunctionArgs(instr) - } else { - evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) - } - // Continue only when all parameter information are available - val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (refinableResults.nonEmpty) { - if (tac.isDefined) { - val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) - returns.foreach { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), 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) - return refinableResults.head - } - - state.nonFinalFunctionArgs.remove(instr) - state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) - if (tac.isDefined) { - state.removeFromMethodPrep2defSite(m, defSite) - // TAC available => Get return UVar 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 - FinalEP(instr, StringConstancyProperty.lb) - } else { - val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), m) - StringAnalysis.registerParams(entity, evaluatedParams) - - val eps = ps(entity, StringConstancyProperty.key) - if (eps.isRefinable) { - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) - } - eps - } - results.find(_.isRefinable).getOrElse(results.head) - } - } else { - // No TAC => Register dependee and continue - state.appendToMethodPrep2defSite(m, defSite) - EPK(state.entity, StringConstancyProperty.key) - } - } -} From 21004481cee838186bc5a6386e841e4d7097093c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 13 Feb 2024 23:11:53 +0100 Subject: [PATCH 372/583] Reduce non-virtual method call handling to l0 level --- .../L0NonVirtualMethodCallInterpreter.scala | 32 +++++-- .../L1InterpretationHandler.scala | 3 +- .../L1NonVirtualMethodCallInterpreter.scala | 84 ------------------- 3 files changed, 25 insertions(+), 94 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index ca1d5c5a0e..5c66f23d5f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -16,7 +16,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStrin import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[NonVirtualMethodCall]]s in an intraprocedural fashion. + * Responsible for processing [[NonVirtualMethodCall]]s without a call graph. * For supported method calls, see the documentation of the `interpret` function. * * @author Maximilian Rüsch @@ -39,14 +39,15 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]] *

    • *
    * - * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. + * For all other calls, a result containing [[StringConstancyInformation.getNeutralElement]] will be returned. */ override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val prop = instr.name match { + val sciOpt = instr.name match { case "" => interpretInit(instr) - case _ => StringConstancyProperty.getNeutralElement + case _ => Some(StringConstancyInformation.getNeutralElement) } - FinalEP(instr, prop) + // IMPROVE DO PROPER DEPENDENCY HANDLING + FinalEP(instr, StringConstancyProperty(sciOpt.getOrElse(StringConstancyInformation.lb))) } /** @@ -56,12 +57,25 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]] * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit(init: T)(implicit state: State): StringConstancyProperty = { + private def interpretInit(init: T)(implicit state: State): Option[StringConstancyInformation] = { init.params.size match { - case 0 => StringConstancyProperty.getNeutralElement + case 0 => Some(StringConstancyInformation.getNeutralElement) case _ => - val scis = init.params.head.asVar.definedBy.toList.flatMap { handleDependentDefSite } - StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis)) + val sciOptsWithPC = init.params.head.asVar.definedBy.toList.map { ds: Int => + (pcOfDefSite(ds)(state.tac.stmts), handleDependentDefSite(ds)) + } + if (sciOptsWithPC.forall(_._2.isDefined)) { + Some(StringConstancyInformation.reduceMultiple(sciOptsWithPC.map(_._2.get))) + } else { + // Some intermediate results => register necessary information from final results and return an + // intermediate result + sciOptsWithPC.foreach { sciOptWithPC => + if (sciOptWithPC._2.isDefined) { + state.appendToFpe2Sci(sciOptWithPC._1, sciOptWithPC._2.get, reset = true) + } + } + None + } } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 93794e73b2..bcba884e1d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -27,6 +27,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.FieldReadFinalizer @@ -302,7 +303,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( nvmc: NonVirtualMethodCall[V], defSite: Int )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val r = L1NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) + val r = L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) => state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala deleted file mode 100644 index fd696cc20e..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ /dev/null @@ -1,84 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * Responsible for processing [[NonVirtualMethodCall]]s in an interprocedural fashion. - * For supported method calls, see the documentation of the `interpret` function. - * - * @author Patrick Mell - */ -case class L1NonVirtualMethodCallInterpreter[State <: L1ComputationState[State]]( - exprHandler: InterpretationHandler[State] -) extends L1StringInterpreter[State] { - - 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. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val e: Integer = defSite - instr.name match { - case "" => interpretInit(instr, e) - case _ => FinalEP(e, StringConstancyProperty.getNeutralElement) - } - } - - /** - * 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: T, defSite: Integer)(implicit - state: State - ): EOptionP[Entity, StringConstancyProperty] = { - init.params.size match { - case 0 => FinalEP(defSite, StringConstancyProperty.getNeutralElement) - case _ => - val results = init.params.head.asVar.definedBy.map { ds: Int => - (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds)) - } - if (results.forall(_._2.isFinal)) { - val reduced = StringConstancyInformation.reduceMultiple(results.map { r => - r._2.asFinal.p.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.isFinal).get._2 - results.foreach { - case (pc, r) => - if (r.isFinal) { - state.appendToFpe2Sci(pc, r.asFinal.p.stringConstancyInformation, reset = true) - } - case _ => - } - returnIR - } - } - } -} From c302ff3170d6cb0a49c8e00df24a0c06bb18dcc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 14 Feb 2024 22:55:12 +0100 Subject: [PATCH 373/583] Revamp interpretation result system --- .../string_analysis/ComputationState.scala | 2 + .../analyses/string_analysis/IPResult.scala | 81 ++++++++++ .../string_analysis/StringAnalysis.scala | 10 +- .../BinaryExprInterpreter.scala | 31 ++-- .../DependingStringInterpreter.scala | 38 ----- .../DoubleValueInterpreter.scala | 29 ++-- .../FloatValueInterpreter.scala | 29 ++-- .../IntegerValueInterpreter.scala | 27 ++-- .../InterpretationHandler.scala | 5 +- .../interpretation/NewInterpreter.scala | 28 ---- .../StringConstInterpreter.scala | 33 ++--- .../interpretation/StringInterpreter.scala | 63 ++++++-- .../string_analysis/l0/L0StringAnalysis.scala | 6 +- .../L0ArrayAccessInterpreter.scala | 31 ++-- .../L0InterpretationHandler.scala | 43 +++--- .../L0NonVirtualMethodCallInterpreter.scala | 43 +++--- .../L0StaticFunctionCallInterpreter.scala | 30 ++-- .../interpretation/L0StringInterpreter.scala | 24 +-- .../L0VirtualFunctionCallInterpreter.scala | 79 +++++----- .../L0VirtualMethodCallInterpreter.scala | 17 +-- .../string_analysis/l1/L1StringAnalysis.scala | 22 +-- .../L1FieldReadInterpreter.scala | 37 ++--- .../L1InterpretationHandler.scala | 138 ++++++++---------- .../L1NewArrayInterpreter.scala | 25 ++-- .../L1NonVirtualFunctionCallInterpreter.scala | 26 ++-- .../interpretation/L1StringInterpreter.scala | 21 --- .../L1VirtualFunctionCallInterpreter.scala | 57 +++----- .../L1VirtualMethodCallInterpreter.scala | 45 ------ .../preprocessing/PathTransformer.scala | 8 +- .../string_analysis/string_analysis.scala | 5 +- 30 files changed, 446 insertions(+), 587 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 316b8d51d4..2c89966819 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -58,6 +58,8 @@ trait ComputationState[State <: ComputationState[State]] { */ var dependees: List[EOptionP[Entity, Property]] = List() + var dependeeDefSites: List[IPResult] = List() + /** * A mapping from DUVar elements to the corresponding values of the FlatPathElements */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala new file mode 100644 index 0000000000..944fddf700 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala @@ -0,0 +1,81 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis + +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation + +/** + * A result of an interpretation of some def site using some [[interpretation.InterpretationHandler]]. + * Essentially a simplified version of [[org.opalj.fpcf.EOptionP]] that has been adapted to the needs of string analyses. + * + * @author Maximilian Rüsch + */ +trait IPResult { + def isNoResult: Boolean + + def isFinal: Boolean + final def isRefinable: Boolean = !isFinal + + def asFinal: FinalIPResult + + def sciOpt: Option[StringConstancyInformation] +} + +/** + * Indicates that the def site is not relevant to the interpretation of the analyses. + * + * @note Since the def site is not relevant, we are able to return a final result with the neutral element when needed. + * Interpreters handling this result type should either convert it to + * [[StringConstancyInformation.getNeutralElement]] or preserve it. + */ +object NoIPResult extends IPResult { + override def isNoResult: Boolean = true + + override def isFinal: Boolean = true + + override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement) + override def sciOpt: Option[StringConstancyInformation] = None +} + +trait SomeIPResult extends IPResult { + override final def isNoResult: Boolean = false +} + +object EmptyIPResult extends SomeIPResult { + def isFinal = false + override def asFinal: FinalIPResult = throw new UnsupportedOperationException() + + override def sciOpt: Option[StringConstancyInformation] = None +} + +trait ValueIPResult extends SomeIPResult { + override final def sciOpt: Option[StringConstancyInformation] = Some(sci) + + def sci: StringConstancyInformation +} + +object ValueIPResult { + def unapply(valueIPResult: ValueIPResult): Some[StringConstancyInformation] = Some(valueIPResult.sci) +} + +case class FinalIPResult(override val sci: StringConstancyInformation) extends ValueIPResult { + override final def isFinal: Boolean = true + override def asFinal: FinalIPResult = this +} + +object FinalIPResult { + def nullElement = new FinalIPResult(StringConstancyInformation.getNullElement) + def lb = new FinalIPResult(StringConstancyInformation.lb) +} + +case class InterimIPResult(override val sci: StringConstancyInformation) extends ValueIPResult { + override final def isFinal = false + override def asFinal: FinalIPResult = throw new UnsupportedOperationException() +} + +object InterimIPResult { + def lb = new InterimIPResult(StringConstancyInformation.lb) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 42322d84d1..744979c7c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -120,8 +120,8 @@ trait StringAnalysis extends FPCFAnalysis { // Update the state 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 + state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = + FinalIPResult(p.stringConstancyInformation) // Housekeeping val index = state.entity2Function(e).indexOf(f) state.entity2Function(e).remove(index) @@ -230,9 +230,9 @@ trait StringAnalysis extends FPCFAnalysis { p.elements.foreach { case fpe: FlatPathElement => if (!state.fpe2sci.contains(fpe.pc)) { - val eOptP = state.iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) - if (eOptP.isFinal) { - state.appendToFpe2Sci(fpe.pc, eOptP.asFinal.p.stringConstancyInformation, reset = true) + val r = state.iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) + if (r.isFinal) { + state.appendToFpe2Sci(fpe.pc, r.asFinal.sci, reset = true) } else { hasFinalResult = false } 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 c4bc20ea0f..b1e8df79b2 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 @@ -8,36 +8,33 @@ package interpretation import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.FinalEP /** - * Responsible for processing [[BinaryExpr]]ions. A list of currently supported binary expressions can be found in the - * documentation of [[interpret]]. - * * @author Maximilian Rüsch */ -object BinaryExprInterpreter extends StringInterpreter[Nothing] { +case class BinaryExprInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { override type T = BinaryExpr[V] /** - * Currently, this implementation supports the interpretation of the following binary - * expressions: + * Currently, this implementation supports the interpretation of the following binary expressions: *
      *
    • [[ComputationalTypeInt]] *
    • [[ComputationalTypeFloat]]
    • * - * For all other expressions, a result containing [[StringConstancyProperty.getNeutralElement]] - * will be returned. + * For all other expressions, a [[NoIPResult]] will be returned. */ - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = { - val sci = instr.cTpe match { - case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt - case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat - case _ => StringConstancyInformation.getNeutralElement + def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + instr.cTpe match { + case ComputationalTypeInt => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicInt) + case ComputationalTypeFloat => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicFloat) + case _ => NoIPResult } - FinalEP(instr, StringConstancyProperty(sci)) } } + +object BinaryExprInterpreter { + + def interpret[State <: ComputationState[State]](instr: BinaryExpr[V], defSite: Int)(implicit state: State): IPResult = + BinaryExprInterpreter[State]().interpret(instr, defSite) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala deleted file mode 100644 index 98fc5ef4df..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DependingStringInterpreter.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalP - -/** - * @author Maximilian Rüsch - */ -trait DependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { - - protected def handleDependentDefSite(defSite: Int)(implicit - state: State, - exprHandler: InterpretationHandler[State] - ): Option[StringConstancyInformation] = { - handleInterpretationResult(exprHandler.processDefSite(defSite)) - } - - protected def handleInterpretationResult(ep: EOptionP[Entity, StringConstancyProperty])(implicit - state: State - ): Option[StringConstancyInformation] = { - ep match { - case FinalP(p) => - Some(p.stringConstancyInformation) - case eps => - state.dependees = eps :: state.dependees - None - } - } -} 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 index fca905af3d..76a1b379c8 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/DoubleValueInterpreter.scala @@ -6,28 +6,29 @@ package analyses package string_analysis package interpretation -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.fpcf.FinalEP /** - * Responsible for processing [[DoubleConst]]s. - * * @author Maximilian Rüsch */ -object DoubleValueInterpreter extends StringInterpreter[Nothing] { +case class DoubleValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { override type T = DoubleConst - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = - FinalEP( - instr, - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) - ) + def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = + FinalIPResult(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + )) +} + +object DoubleValueInterpreter { + + def interpret[State <: ComputationState[State]](instr: DoubleConst, defSite: Int)(implicit + state: State + ): FinalIPResult = + DoubleValueInterpreter[State]().interpret(instr, defSite) } 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 index f98b02aa06..4ebbeb7374 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/FloatValueInterpreter.scala @@ -6,28 +6,29 @@ package analyses package string_analysis package interpretation -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.fpcf.FinalEP /** - * Responsible for processing [[FloatConst]]s. - * * @author Maximilian Rüsch */ -object FloatValueInterpreter extends StringInterpreter[Nothing] { +case class FloatValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { override type T = FloatConst - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = - FinalEP( - instr, - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) - ) + def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = + FinalIPResult(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + )) +} + +object FloatValueInterpreter { + + def interpret[State <: ComputationState[State]](instr: FloatConst, defSite: Int)(implicit + state: State + ): FinalIPResult = + FloatValueInterpreter[State]().interpret(instr, defSite) } 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 27b742272d..94eef50964 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 @@ -7,28 +7,27 @@ package analyses package string_analysis package interpretation -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.fpcf.FinalEP /** - * Responsible for processing [[IntConst]]s. - * * @author Maximilian Rüsch */ -object IntegerValueInterpreter extends StringInterpreter[Nothing] { +case class IntegerValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { override type T = IntConst - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = - FinalEP( - instr, - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) - ) + def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = + FinalIPResult(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + )) +} + +object IntegerValueInterpreter { + + def interpret[State <: ComputationState[State]](instr: IntConst, defSite: Int)(implicit state: State): FinalIPResult = + IntegerValueInterpreter[State]().interpret(instr, defSite) } 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 3f18041583..57e787548d 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 @@ -10,12 +10,9 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer 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.fpcf.Entity -import org.opalj.fpcf.EOptionP abstract class InterpretationHandler[State <: ComputationState[State]] { @@ -40,7 +37,7 @@ abstract class InterpretationHandler[State <: ComputationState[State]] { * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). * The entity of the result will be the given `defSite`. */ - def processDefSite(defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] + def processDefSite(defSite: Int)(implicit state: State): IPResult /** * [[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/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala deleted file mode 100644 index 323b6f1364..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FinalEP - -/** - * Responsible for processing [[New]] expressions. - * - * @author Maximilian Rüsch - */ -object NewInterpreter extends StringInterpreter[Nothing] { - - override type T = New - - /** - * [[New]] expressions do not carry any relevant information in this context (as the initial values are not set in - * [[New]] expressions but, e.g., in [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation - * always returns a [[StringConstancyProperty.getNeutralElement]]. - */ - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = - FinalEP(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 9b3b5eaeb5..7b8d4cfd69 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 @@ -6,33 +6,30 @@ package analyses package string_analysis package interpretation -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.fpcf.FinalEP import org.opalj.tac.StringConst /** - * Responsible for processing [[StringConst]]s. - * * @author Maximilian Rüsch */ -object StringConstInterpreter extends StringInterpreter[Nothing] { +case class StringConstInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { override type T = StringConst - /** - * Always returns a list with one [[StringConstancyLevel.CONSTANT]] [[StringConstancyInformation]] element holding - * the string const value. - */ - def interpret(instr: T): FinalEP[T, StringConstancyProperty] = - FinalEP( - instr, - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value - )) - ) + def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = + FinalIPResult(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value + )) +} + +object StringConstInterpreter { + + def interpret[State <: ComputationState[State]](instr: StringConst, defSite: Int)(implicit + state: State + ): FinalIPResult = + StringConstInterpreter[State]().interpret(instr, defSite) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 94137cebb9..d23f1fc594 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -10,11 +10,8 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.Method -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI @@ -25,6 +22,24 @@ trait StringInterpreter[State <: ComputationState[State]] { 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. + * @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). + *

      + * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int)(implicit state: State): IPResult + /** * 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 @@ -90,21 +105,21 @@ trait StringInterpreter[State <: ComputationState[State]] { case (nextParamList, outerIndex) => ListBuffer.from(nextParamList.zipWithIndex.map { case (nextParam, middleIndex) => + val e = (nextParam.asVar.toPersistentForm(state.tac.stmts), state.dm.definedMethod) ListBuffer.from(nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { case (ds, innerIndex) => - val ep = iHandler.processDefSite(ds) - if (ep.isRefinable) { + val result = iHandler.processDefSite(ds) + if (result.isRefinable) { if (!state.nonFinalFunctionArgsPos.contains(funCall)) { state.nonFinalFunctionArgsPos(funCall) = mutable.Map() } - val e = ep.e.asInstanceOf[SContext] state.nonFinalFunctionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) if (!state.entity2Function.contains(e)) { state.entity2Function(e) = ListBuffer() } state.entity2Function(e).append(funCall) } - ep + result }) }) }) @@ -113,9 +128,7 @@ trait StringInterpreter[State <: ComputationState[State]] { * Checks whether the interpretation of parameters, as, e.g., produced by [[evaluateParameters()]], is final or not * and returns all refinable results as a list. Hence, an empty list is returned, all parameters are fully evaluated. */ - protected def getRefinableParameterResults( - evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, StringConstancyProperty]]]] - ): List[EOptionP[Entity, StringConstancyProperty]] = + protected def getRefinableParameterResults(evaluatedParameters: Seq[Seq[Seq[IPResult]]]): List[IPResult] = evaluatedParameters.flatten.flatten.filter { _.isRefinable }.toList /** @@ -125,11 +138,33 @@ trait StringInterpreter[State <: ComputationState[State]] { * all results in the inner-most sequence are final! */ protected def convertEvaluatedParameters( - evaluatedParameters: Seq[Seq[Seq[FinalEP[Entity, StringConstancyProperty]]]] + evaluatedParameters: Seq[Seq[Seq[FinalIPResult]]] ): ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer.from(evaluatedParameters.map { paramList => - ListBuffer.from(paramList.map { param => - StringConstancyInformation.reduceMultiple(param.map { _.p.stringConstancyInformation }) - }) + ListBuffer.from(paramList.map { param => StringConstancyInformation.reduceMultiple(param.map { _.sci }) }) }) } + +/** + * @author Maximilian Rüsch + */ +trait DependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { + + protected def handleDependentDefSite(defSite: Int)(implicit + state: State, + exprHandler: InterpretationHandler[State] + ): IPResult = { + exprHandler.processDefSite(defSite) match { + case ipr: FinalIPResult => + state.dependeeDefSites = state.dependeeDefSites.filter(_ != ipr) + ipr + case ipr: InterimIPResult => + if (!state.dependeeDefSites.contains(ipr)) { + state.dependeeDefSites = ipr :: state.dependeeDefSites + } + ipr + case ipr => + ipr + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index cf5defcd9f..1b5989d149 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -118,7 +118,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val r = state.iHandler.processDefSite(defSites.head)(state) - return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) + return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) } val expr = stmts(defSites.head).asAssignment.expr @@ -155,9 +155,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } else { // We deal with pure strings TODO unify result handling val sci = StringConstancyInformation.reduceMultiple( - uVar.definedBy.toArray.sorted.map { ds => - state.iHandler.processDefSite(ds).asFinal.p.stringConstancyInformation - } + uVar.definedBy.toArray.sorted.map { ds => state.iHandler.processDefSite(ds).asFinal.sci } ) if (state.dependees.isEmpty) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 2c871ff89f..b81dcc4429 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -7,17 +7,13 @@ package string_analysis package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.collection.immutable.IntTrieSet -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions in an intraprocedural fashion. + * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions without a call graph. * * @author Maximilian Rüsch */ @@ -25,22 +21,20 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { - implicit val _exprHandler: InterpretationHandler[State] = exprHandler - override type T = ArrayLoad[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { implicit val stmts: Array[Stmt[V]] = state.tac.stmts val allDefSitesByPC = L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr).map(ds => (pcOfDefSite(ds), ds)).toMap - val sciOpts = allDefSitesByPC.keys.toList.sorted.map { pc => - (pc, handleDependentDefSite(allDefSitesByPC(pc))) + val results = allDefSitesByPC.keys.toList.sorted.map { pc => + (pc, handleDependentDefSite(allDefSitesByPC(pc))(state, exprHandler)) }.map { - case (pc, sciOpt) => - if (sciOpt.isDefined) - state.appendToFpe2Sci(pc, sciOpt.get) - sciOpt + case (pc, result) => + if (result.isFinal) + state.appendToFpe2Sci(pc, result.asFinal.sci) + result } // Add information of parameters @@ -51,18 +45,17 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( state.appendToFpe2Sci(pc, sci) } - val unfinishedDependees = sciOpts.exists(_.isEmpty) + val unfinishedDependees = results.exists(_.isRefinable) if (unfinishedDependees) { - // IMPROVE return interim here - FinalEP((instr.arrayRef.asVar, state.entity._2), StringConstancyProperty.lb) + InterimIPResult.lb } else { - var resultSci = StringConstancyInformation.reduceMultiple(sciOpts.map(_.get)) + var resultSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) if (resultSci.isTheNeutralElement) { resultSci = StringConstancyInformation.lb } state.appendToFpe2Sci(pcOfDefSite(defSite), resultSci) - FinalEP((instr.arrayRef.asVar, state.entity._2), StringConstancyProperty(resultSci)) + FinalIPResult(resultSci) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 7f3d8d9957..6a37e8301e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -9,18 +9,13 @@ package interpretation import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter /** @@ -44,56 +39,52 @@ class L0InterpretationHandler[State <: L0ComputationState[State]]()( *

      * @inheritdoc */ - override def processDefSite(defSite: Int)(implicit - state: State - ): 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 + override def processDefSite(defSite: Int)(implicit state: State): IPResult = { val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) if (defSite < 0) { val params = state.params.toList.map(_.toList) if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.lb) - return FinalEP(e, StringConstancyProperty.lb) + return FinalIPResult.lb } else { val sci = getParam(params, defSite) state.appendToInterimFpe2Sci(defSitePC, sci) - return FinalEP(e, StringConstancyProperty(sci)) + return FinalIPResult(sci) } } else if (processedDefSites.contains(defSite)) { state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) - return FinalEP(e, StringConstancyProperty.getNeutralElement) + return FinalIPResult(StringConstancyInformation.getNeutralElement) } processedDefSites(defSite) = () state.tac.stmts(defSite) match { case Assignment(_, _, expr: StringConst) => - StringConstInterpreter.interpret(expr) + StringConstInterpreter.interpret(expr, defSite)(state) case Assignment(_, _, expr: IntConst) => - IntegerValueInterpreter.interpret(expr) + IntegerValueInterpreter.interpret(expr, defSite)(state) case Assignment(_, _, expr: FloatConst) => - FloatValueInterpreter.interpret(expr) + FloatValueInterpreter.interpret(expr, defSite)(state) case Assignment(_, _, expr: DoubleConst) => - DoubleValueInterpreter.interpret(expr) + DoubleValueInterpreter.interpret(expr, defSite)(state) case Assignment(_, _, expr: BinaryExpr[V]) => - BinaryExprInterpreter.interpret(expr) + BinaryExprInterpreter.interpret(expr, defSite)(state) case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(this).interpret(expr, defSite)(state) - case Assignment(_, _, expr: New) => - NewInterpreter.interpret(expr) - case Assignment(_, _, expr: GetField[V]) => + case Assignment(_, _, _: New) => + state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) + NoIPResult + case Assignment(_, _, _: GetField[V]) => // Currently unsupported - FinalEP(expr, StringConstancyProperty.lb) + FinalIPResult.lb case Assignment(_, _, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + case Assignment(_, _, _: NonVirtualFunctionCall[V]) => // Currently unsupported - FinalEP(expr, StringConstancyProperty.lb) + FinalIPResult.lb case ExprStmt(_, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => @@ -104,7 +95,7 @@ class L0InterpretationHandler[State <: L0ComputationState[State]]()( L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) case _ => state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) - FinalEP(e, StringConstancyProperty.getNeutralElement) + NoIPResult } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 5c66f23d5f..3dbc361ef3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -7,17 +7,12 @@ package string_analysis package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Responsible for processing [[NonVirtualMethodCall]]s without a call graph. - * For supported method calls, see the documentation of the `interpret` function. * * @author Maximilian Rüsch */ @@ -39,42 +34,40 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]] * *

    * - * For all other calls, a result containing [[StringConstancyInformation.getNeutralElement]] will be returned. + * For all other calls, a [[NoIPResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val sciOpt = instr.name match { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + instr.name match { case "" => interpretInit(instr) - case _ => Some(StringConstancyInformation.getNeutralElement) + case _ => NoIPResult } - // IMPROVE DO PROPER DEPENDENCY HANDLING - FinalEP(instr, StringConstancyProperty(sciOpt.getOrElse(StringConstancyInformation.lb))) } /** - * 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). + * Processes an `<init>` method call. If it has no parameters, [[NoIPResult]] 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: T)(implicit state: State): Option[StringConstancyInformation] = { + private def interpretInit(init: T)(implicit state: State): IPResult = { init.params.size match { - case 0 => Some(StringConstancyInformation.getNeutralElement) + case 0 => NoIPResult case _ => - val sciOptsWithPC = init.params.head.asVar.definedBy.toList.map { ds: Int => + val resultsWithPC = init.params.head.asVar.definedBy.toList.map { ds: Int => (pcOfDefSite(ds)(state.tac.stmts), handleDependentDefSite(ds)) } - if (sciOptsWithPC.forall(_._2.isDefined)) { - Some(StringConstancyInformation.reduceMultiple(sciOptsWithPC.map(_._2.get))) + if (resultsWithPC.forall(_._2.isFinal)) { + FinalIPResult(StringConstancyInformation.reduceMultiple(resultsWithPC.map(_._2.asFinal.sci))) } else { // Some intermediate results => register necessary information from final results and return an // intermediate result - sciOptsWithPC.foreach { sciOptWithPC => - if (sciOptWithPC._2.isDefined) { - state.appendToFpe2Sci(sciOptWithPC._1, sciOptWithPC._2.get, reset = true) + // IMPROVE DO PROPER DEPENDENCY HANDLING + resultsWithPC.foreach { resultWithPC => + if (resultWithPC._2.isFinal) { + state.appendToFpe2Sci(resultWithPC._1, resultWithPC._2.asFinal.sci, reset = true) } } - None + InterimIPResult.lb } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index e95b642a71..9fd1d4b285 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -13,10 +13,6 @@ import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler @@ -35,7 +31,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { processStringValueOf(instr) } else { @@ -45,16 +41,16 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( private def processStringValueOf(call: StaticFunctionCall[V])( implicit state: State - ): EOptionP[Entity, StringConstancyProperty] = { + ): IPResult = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { exprHandler.processDefSite(_) } val interim = results.find(_.isRefinable) if (interim.isDefined) { - interim.get + InterimIPResult.lb } else { // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r => r.asFinal.p.stringConstancyInformation } + val scis = results.map { r => r.asFinal.sci } val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { scis.map { sci => if (Try(sci.possibleStrings.toInt).isSuccess) { @@ -66,17 +62,17 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } else { scis } - FinalEP(call, StringConstancyProperty(StringConstancyInformation.reduceMultiple(finalScis))) + FinalIPResult(StringConstancyInformation.reduceMultiple(finalScis)) } } protected def processArbitraryCall(instr: T, defSite: Int)(implicit state: State - ): EOptionP[Entity, StringConstancyProperty] = { + ): IPResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalEP(instr, StringConstancyProperty.lb) + return FinalIPResult(StringConstancyInformation.lb) } // Collect all parameters; either from the state if the interpretation of instr was started before (in this case, @@ -105,7 +101,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } state.nonFinalFunctionArgs(instr) = params state.appendToMethodPrep2defSite(m, defSite) - return refinableResults.head + return InterimIPResult.lb } state.nonFinalFunctionArgs.remove(instr) @@ -118,7 +114,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, is approximated // with the lower bound - FinalEP(instr, StringConstancyProperty.lb) + FinalIPResult.lb } else { val results = returns.map { ret => val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m) @@ -131,12 +127,16 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } eps } - results.find(_.isRefinable).getOrElse(results.head) + if (results.exists(_.isRefinable)) { + InterimIPResult.lb + } else { + FinalIPResult(results.head.asFinal.p.stringConstancyInformation) + } } } else { // No TAC => Register dependee and continue state.appendToMethodPrep2defSite(m, defSite) - EPK(state.entity, StringConstancyProperty.key) + EmptyIPResult } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index 2d7d47982b..e1a0ad2a88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -7,31 +7,9 @@ package string_analysis package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter /** * @author Maximilian Rüsch */ -trait L0StringInterpreter[State <: L0ComputationState[State]] extends StringInterpreter[State] { - - /** - * @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). - *

    - * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] -} +trait L0StringInterpreter[State <: L0ComputationState[State]] extends StringInterpreter[State] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 66bacf18eb..def0f292bc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -15,13 +15,9 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.DoubleType import org.opalj.br.FloatType 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.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.value.TheIntegerValue @@ -40,13 +36,13 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( override type T = VirtualFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { val result = handleInterpretation(instr, defSite) - if (result.isDefined) { - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.get) + if (result.sciOpt.isDefined) { + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sciOpt.get) } - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(result.getOrElse(StringConstancyInformation.lb))) + result } /** @@ -64,31 +60,28 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * [[L0VirtualFunctionCallInterpreter.interpretReplaceCall]]. * *

  • - * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] - * will be returned in case the passed method returns a [[java.lang.String]]. + * Apart from these supported methods, a [[StringConstancyInformation.lb]] will be returned in case the passed + * method returns a [[java.lang.String]]. *
  • * * - * If none of the above-described cases match, a result containing - * [[StringConstancyProperty.getNeutralElement]] will be returned. + * If none of the above-described cases match, a [[NoResult]] will be returned. */ - protected def handleInterpretation(instr: T, defSite: Int)(implicit - state: State - ): Option[StringConstancyInformation] = { + protected def handleInterpretation(instr: T, defSite: Int)(implicit state: State): IPResult = { instr.name match { case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) - case "replace" => Some(interpretReplaceCall) + case "replace" => interpretReplaceCall case "substring" if instr.descriptor.returnType == ObjectType.String => interpretSubstringCall(instr) case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj == ObjectType.String => Some(StringConstancyInformation.lb) - case FloatType | DoubleType => Some(StringConstancyInformation( + case obj: ObjectType if obj == ObjectType.String => FinalIPResult.lb + case FloatType | DoubleType => FinalIPResult(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.FloatValue )) - case _ => Some(StringConstancyInformation.getNeutralElement) + case _ => NoIPResult } } } @@ -98,14 +91,12 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * 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])(implicit - state: State - ): Option[StringConstancyInformation] = { + private def interpretAppendCall(appendCall: T)(implicit state: State): IPResult = { val receiverSci = receiverValuesOfCall(appendCall) val appendSci = valueOfAppendCall(appendCall) if (appendSci.isEmpty) { - None + InterimIPResult.lb } else { val sci = if (receiverSci.isTheNeutralElement && appendSci.get.isTheNeutralElement) { // although counter-intuitive, this case may occur if both the receiver and the parameter have been @@ -128,7 +119,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( ) } - Some(sci) + FinalIPResult(sci) } } @@ -136,23 +127,21 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * 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])(implicit - state: State - ): Option[StringConstancyInformation] = { + private def valueOfAppendCall(call: T)(implicit state: State): Option[StringConstancyInformation] = { 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 = handleDependentDefSite(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.isDefined && value.get.isTheNeutralElement) { + // 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.isNoResult) { value = handleDependentDefSite(state.tac.stmts(defSiteHead).asAssignment.targetVar.usedBy.toArray.min) } - if (value.isEmpty) { + if (value.isRefinable) { None } else { - val sci = value.get + val sci = value.asFinal.sci val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt => @@ -186,37 +175,37 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( /** * Processes calls to [[String#substring]]. */ - private def interpretSubstringCall(substringCall: T)(implicit state: State): Option[StringConstancyInformation] = { + private def interpretSubstringCall(substringCall: T)(implicit state: State): IPResult = { val receiverSci = receiverValuesOfCall(substringCall) if (receiverSci.isComplex) { // We cannot yet interpret substrings of mixed values - Some(StringConstancyInformation.lb) + FinalIPResult.lb } else { val parameterCount = substringCall.params.size parameterCount match { case 1 => substringCall.params.head.asVar.value match { case intValue: TheIntegerValue => - Some(StringConstancyInformation( + FinalIPResult(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.REPLACE, receiverSci.possibleStrings.substring(intValue.value) )) case _ => - Some(StringConstancyInformation.lb) + FinalIPResult.lb } case 2 => (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => - Some(StringConstancyInformation( + FinalIPResult(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) )) case _ => - Some(StringConstancyInformation.lb) + FinalIPResult.lb } case _ => throw new IllegalStateException( @@ -234,8 +223,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( // only the head as a single receiver interpretation will produce one element val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => // IMPROVE enable handling dependees here - val r = exprHandler.processDefSite(ds) - r.asFinal.p.stringConstancyInformation + exprHandler.processDefSite(ds).asFinal.sci }.filter { sci => !sci.isTheNeutralElement } scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement) } @@ -244,14 +232,13 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * Processes 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: T)(implicit state: State): Option[StringConstancyInformation] = { - handleInterpretationResult(exprHandler.processDefSite(call.receiver.asVar.definedBy.head)) + private def interpretToStringCall(call: T)(implicit state: State): IPResult = { + handleDependentDefSite(call.receiver.asVar.definedBy.head) } /** - * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. (Currently, this function simply - * approximates `replace` functions by returning the lower bound of [[StringConstancyProperty]]). + * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall: StringConstancyInformation = - InterpretationHandler.getStringConstancyInformationForReplace + private def interpretReplaceCall: IPResult = + FinalIPResult(InterpretationHandler.getStringConstancyInformationForReplace) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index a6ec6dd9b6..83535a8ded 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -7,13 +7,9 @@ package string_analysis package l0 package interpretation -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.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP /** * Responsible for processing [[VirtualMethodCall]]s in an intraprocedural fashion. @@ -36,13 +32,14 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() * * * - * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will be returned. + * For all other calls, a [[NoIPResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val sci = instr.name match { - case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) - case _ => StringConstancyInformation.getNeutralElement + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + instr.name match { + case "setLength" => FinalIPResult( + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) + ) + case _ => NoIPResult } - FinalEP(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 751fa77a8a..44807e21f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -232,7 +232,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val r = state.iHandler.processDefSite(defSites.head)(state) - return Result(state.entity, StringConstancyProperty(r.asFinal.p.stringConstancyInformation)) + return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) } val call = stmts(defSites.head).asAssignment.expr @@ -261,15 +261,17 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } } - var sci = StringConstancyInformation.lb - if (attemptFinalResultComputation - && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath)(state) - ) { - sci = new PathTransformer(state.iHandler) - .pathToStringTree(state.computedLeanPath, state.fpe2sci) - .reduce(true) - } + val sci = + if (attemptFinalResultComputation + && state.dependees.isEmpty + && computeResultsForPath(state.computedLeanPath)(state) + ) { + new PathTransformer(state.iHandler) + .pathToStringTree(state.computedLeanPath, state.fpe2sci) + .reduce(true) + } else { + StringConstancyInformation.lb + } if (state.dependees.nonEmpty) { getInterimResult(state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 68f98ad27f..4d1416de98 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -16,10 +16,6 @@ import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.log.Error import org.opalj.log.Info @@ -74,21 +70,21 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { - return FinalEP(instr, StringConstancyProperty.lb) + return FinalIPResult.lb } val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { - return FinalEP(instr, StringConstancyProperty.lb) + return FinalIPResult.lb } var hasInit = false - val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() + val results = ListBuffer[IPResult]() writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod @@ -99,9 +95,9 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( val (tacEps, tac) = getTACAI(ps, method, state) val nextResult = if (parameter.isEmpty) { // Field parameter information is not available - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) + FinalIPResult.lb } else if (tacEps.isRefinable) { - EPK(state.entity, StringConstancyProperty.key) + EmptyIPResult } else { tac match { case Some(_) => @@ -115,11 +111,13 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( // though it might not be. Thus, we use -1 as it is a safe dummy // value state.appendToVar2IndexMapping(entity._1, -1) + InterimIPResult(eps.lb.stringConstancyInformation) + } else { + FinalIPResult(eps.asFinal.p.stringConstancyInformation) } - eps case _ => // No TAC available - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty.lb) + FinalIPResult.lb } } results.append(nextResult) @@ -133,25 +131,20 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" ) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(sci)) + FinalIPResult(sci) } else { 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(FinalEP( - instr, - StringConstancyProperty(StringConstancyInformation.getNullElement) - )) + results.append(FinalIPResult.nullElement) } - val finalSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.stringConstancyInformation - }) + val finalSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) - FinalEP(defSite.asInstanceOf[Integer], StringConstancyProperty(finalSci)) + FinalIPResult(finalSci) } else { - results.find(!_.isFinal).get + InterimIPResult.lb } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index bcba884e1d..f1dbca1522 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -12,23 +12,18 @@ import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.FieldReadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer @@ -59,42 +54,42 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( * * @inheritdoc */ - override def processDefSite(defSite: Int)(implicit - state: State - ): 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 + override def processDefSite(defSite: Int)(implicit state: State): IPResult = { // Function parameters are not evaluated when none are present (this always includes the // implicit parameter for "this" and for exceptions thrown outside the current function) if (defSite < 0) { val params = state.params.toList.map(_.toList) if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalEP(e, StringConstancyProperty.lb) + return FinalIPResult.lb } else { val sci = getParam(params, defSite) state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - return FinalEP(e, StringConstancyProperty(sci)) + return FinalIPResult(sci) } } else if (processedDefSites.contains(defSite)) { state.appendToInterimFpe2Sci( pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.getNeutralElement ) - return FinalEP(e, StringConstancyProperty.getNeutralElement) + return NoIPResult } // Note that def sites referring to constant expressions will be deleted further down processedDefSites(defSite) = () state.tac.stmts(defSite) match { - 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) // TODO what about long consts - case Assignment(_, _, expr: ArrayLoad[V]) => processArrayLoad(expr, defSite) - case Assignment(_, _, expr: NewArray[V]) => processNewArray(expr, defSite) - case Assignment(_, _, expr: New) => processNew(expr, defSite) + 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) // TODO what about long consts + case Assignment(_, _, expr: ArrayLoad[V]) => processArrayLoad(expr, defSite) + case Assignment(_, _, expr: NewArray[V]) => processNewArray(expr, defSite) + case Assignment(_, _, _: New) => + state.appendToInterimFpe2Sci( + pcOfDefSite(defSite)(state.tac.stmts), + StringConstancyInformation.getNeutralElement + ) + NoIPResult case Assignment(_, _, expr: GetStatic) => processGetField(expr, defSite) case ExprStmt(_, expr: GetStatic) => processGetField(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite) @@ -111,7 +106,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.getNeutralElement ) - FinalEP(e, StringConstancyProperty.getNeutralElement) + NoIPResult } } @@ -122,19 +117,18 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processConstExpr( constExpr: SimpleValueConst, defSite: Int - )(implicit state: State): FinalEP[Entity, StringConstancyProperty] = { - val finalEP = constExpr match { - case ic: IntConst => IntegerValueInterpreter.interpret(ic) - case fc: FloatConst => FloatValueInterpreter.interpret(fc) - case dc: DoubleConst => DoubleValueInterpreter.interpret(dc) - case sc: StringConst => StringConstInterpreter.interpret(sc) + )(implicit state: State): FinalIPResult = { + val result = constExpr match { + case ic: IntConst => IntegerValueInterpreter.interpret(ic, defSite)(state) + case fc: FloatConst => FloatValueInterpreter.interpret(fc, defSite)(state) + case dc: DoubleConst => DoubleValueInterpreter.interpret(dc, defSite)(state) + case sc: StringConst => StringConstInterpreter.interpret(sc, defSite)(state) case c => throw new IllegalArgumentException(s"Unsupported const value: $c") } - val sci = finalEP.p.stringConstancyInformation - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sci) + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sci) processedDefSites.remove(defSite) - finalEP + result } /** @@ -143,10 +137,10 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processArrayLoad( expr: ArrayLoad[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = new L0ArrayAccessInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { - r.asFinal.p.stringConstancyInformation + r.asFinal.sci } else { processedDefSites.remove(defSite) StringConstancyInformation.lb @@ -161,10 +155,10 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processNewArray( expr: NewArray[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = new L1NewArrayInterpreter(this).interpret(expr, defSite) val sci = if (r.isFinal) { - r.asFinal.p.stringConstancyInformation + r.asFinal.sci } else { processedDefSites.remove(defSite) StringConstancyInformation.lb @@ -173,25 +167,13 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( r } - /** - * Helper / utility function for processing [[New]] expressions. - */ - private def processNew(expr: New, defSite: Int)(implicit - state: State - ): FinalEP[Entity, StringConstancyProperty] = { - val finalEP = NewInterpreter.interpret(expr) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalEP.p.stringConstancyInformation) - finalEP - } - /** * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ private def processVFC( expr: VirtualFunctionCall[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).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 @@ -230,36 +212,29 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processStaticFunctionCall( expr: StaticFunctionCall[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) if (r.isRefinable || 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)(implicit - state: State - ): FinalEP[Entity, StringConstancyProperty] = { + private def processBinaryExpr(expr: BinaryExpr[V], defSite: Int)(implicit state: State): IPResult = { // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - val result = BinaryExprInterpreter.interpret(expr) - val sci = result.p.stringConstancyInformation - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + val result = BinaryExprInterpreter.interpret(expr, defSite)(state) + doInterimResultHandling(result, defSite) result } /** * Helper / utility function for processing [[GetField]]s. */ - private def processGetField(expr: FieldRead[V], defSite: Int)(implicit - state: State - ): EOptionP[Entity, StringConstancyProperty] = { + private def processGetField(expr: FieldRead[V], defSite: Int)(implicit state: State): IPResult = { val r = L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider) .interpret(expr, defSite)(state) if (r.isRefinable) { @@ -275,7 +250,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processNonVirtualFunctionCall( expr: NonVirtualFunctionCall[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -290,8 +265,8 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( def processVirtualMethodCall( expr: VirtualMethodCall[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { - val r = L1VirtualMethodCallInterpreter().interpret(expr, defSite)(state) + )(implicit state: State): IPResult = { + val r = L0VirtualMethodCallInterpreter().interpret(expr, defSite)(state) doInterimResultHandling(r, defSite) r } @@ -302,12 +277,15 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( private def processNonVirtualMethodCall( nvmc: NonVirtualMethodCall[V], defSite: Int - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + )(implicit state: State): IPResult = { val r = L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) r match { - case FinalEP(_, p: StringConstancyProperty) => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), p.stringConstancyInformation) + case FinalIPResult(sci) => + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + case InterimIPResult(interimSci) => + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), interimSci) + processedDefSites.remove(defSite) case _ => state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) processedDefSites.remove(defSite) @@ -316,20 +294,18 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( } /** - * 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. + * Takes a result, which can be final or not, as well as a definition site. Takes the steps necessary to provide + * information for computing intermediate results. */ - private def doInterimResultHandling( - result: EOptionP[Entity, Property], - defSite: Int - )(implicit state: State): Unit = { - val sci = if (result.isFinal) { - result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - } else { - StringConstancyInformation.lb + private def doInterimResultHandling(result: IPResult, defSite: Int)(implicit state: State): Unit = { + result match { + case FinalIPResult(sci) => + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) + case InterimIPResult(interimSci) => + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), interimSci) + case _ => + state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) } - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 0764ccf69e..4562617a01 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -7,11 +7,7 @@ package string_analysis package l1 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -36,10 +32,10 @@ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( * definition sites producing a refinable result will have to be handled later on to * not miss this information. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { // Only support for 1-D arrays if (instr.counts.length != 1) { - return FinalEP(instr, StringConstancyProperty.lb) + return FinalIPResult.lb } // Get all sites that define array values and process them @@ -52,7 +48,7 @@ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { d => val r = exprHandler.processDefSite(d) if (r.isFinal) { - state.appendToFpe2Sci(pcOfDefSite(d)(state.tac.stmts), r.asFinal.p.stringConstancyInformation) + state.appendToFpe2Sci(pcOfDefSite(d)(state.tac.stmts), r.asFinal.sci) } r } @@ -61,31 +57,28 @@ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( // Add information of parameters arrValuesDefSites.filter(_ < 0).foreach { ds => val paramPos = Math.abs(ds + 2) - // lb is the fallback value + // IMPROVE should we use lb as the fallback value val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) state.appendToFpe2Sci(pcOfDefSite(ds)(state.tac.stmts), sci) - val e: Integer = ds - allResults ::= FinalEP(e, StringConstancyProperty(sci)) + allResults ::= FinalIPResult(sci) } val interims = allResults.find(!_.isFinal) if (interims.isDefined) { - interims.get + InterimIPResult.lb } else { - var resultSci = StringConstancyInformation.reduceMultiple(allResults.map { - _.asFinal.p.stringConstancyInformation - }) + var resultSci = StringConstancyInformation.reduceMultiple(allResults.map(_.asFinal.sci)) // 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)) + val toAppend = FinalIPResult(resultSci) allResults = toAppend :: allResults } state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), resultSci) - FinalEP(Integer.valueOf(defSite), StringConstancyProperty(resultSci)) + FinalIPResult(resultSci) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 23e6935cea..05ceb5ec47 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -9,14 +9,10 @@ package interpretation import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore /** - * Responsible for processing [[NonVirtualFunctionCall]]s in an interprocedural fashion. + * Responsible for processing [[NonVirtualFunctionCall]]s with a call graph. * * @author Maximilian Rüsch */ @@ -27,13 +23,11 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit - state: State - ): EOptionP[Entity, StringConstancyProperty] = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { val methods = getMethodsForPC(instr.pc) if (methods._1.isEmpty) { // No methods available => Return lower bound - return FinalEP(instr, StringConstancyProperty.lb) + return FinalIPResult.lb } val m = methods._1.head @@ -43,9 +37,9 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State // 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 - FinalEP(instr, StringConstancyProperty.lb) + // A function without returns, e.g., because it is guaranteed to throw an exception, is approximated + // with the lower bound + FinalIPResult.lb } else { val results = returns.map { ret => val puVar = ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts) @@ -58,11 +52,15 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State } eps } - results.find(_.isRefinable).getOrElse(results.head) + if (results.exists(_.isRefinable)) { + InterimIPResult.lb + } else { + FinalIPResult(results.head.asFinal.p.stringConstancyInformation) + } } } else { state.appendToMethodPrep2defSite(m, defSite) - EPK(state.entity, StringConstancyProperty.key) + EmptyIPResult } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 8173bd5be9..5ef5853998 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -12,9 +12,6 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter @@ -23,24 +20,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpre */ trait L1StringInterpreter[State <: L1ComputationState[State]] extends StringInterpreter[State] { - /** - * @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). - *

    - * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int)(implicit state: State): EOptionP[Entity, StringConstancyProperty] - /** * 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`, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 2179b5fdb0..3829a124b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -15,11 +15,8 @@ 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.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter @@ -64,10 +61,9 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( * * @note This function takes care of updating [[ComputationState.fpe2sci]] as necessary. */ - override protected def handleInterpretation(instr: T, defSite: Int)(implicit state: State - ): Option[StringConstancyInformation] = { + ): IPResult = { instr.name match { case "append" => interpretAppendCall(instr) case "toString" | "replace" => super.handleInterpretation(instr, defSite) @@ -89,11 +85,11 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( */ private def interpretArbitraryCall(instr: T, defSite: Int)( implicit state: State - ): Option[StringConstancyInformation] = { + ): IPResult = { val (methods, _) = getMethodsForPC(instr.pc) if (methods.isEmpty) { - return Some(StringConstancyInformation.lb) + return FinalIPResult.lb } // TODO: Type Iterator! val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) @@ -118,7 +114,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (refinableResults.nonEmpty) { state.nonFinalFunctionArgs(instr) = params - return None + return InterimIPResult.lb } state.nonFinalFunctionArgs.remove(instr) @@ -159,12 +155,10 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( } } - val finalResults = results.filter(_.isFinal) - // val intermediateResults = results.filter(_.isRefinable) - if (results.length == finalResults.length) { - Some(finalResults.head.asFinal.p.stringConstancyInformation) + if (results.forall(_.isFinal)) { + FinalIPResult(results.head.asFinal.p.stringConstancyInformation) } else { - None + InterimIPResult.lb } } @@ -175,18 +169,16 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( */ private def interpretAppendCall(appendCall: VirtualFunctionCall[V])( implicit state: State - ): Option[StringConstancyInformation] = { + ): IPResult = { val receiverResults = receiverValuesOfAppendCall(appendCall) val appendResult = valueOfAppendCall(appendCall) if (receiverResults.head.isRefinable || appendResult.isRefinable) { - return None + return InterimIPResult.lb } - val receiverScis = receiverResults.map { - _.asFinal.p.stringConstancyInformation - } - val appendSci = appendResult.asFinal.p.stringConstancyInformation + val receiverScis = receiverResults.map { _.asFinal.sci } + val appendSci = appendResult.asFinal.sci // 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 @@ -215,7 +207,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( ) } - Some(finalSci) + FinalIPResult(finalSci) } /** @@ -230,22 +222,19 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] - )(implicit state: State): List[EOptionP[Entity, StringConstancyProperty]] = { + )(implicit state: State): List[IPResult] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) val finalResults = allResults.filter(_._2.isFinal) val finalResultsWithoutNeutralElements = finalResults.filter { - case (_, FinalEP(_, p: StringConstancyProperty)) => - !p.stringConstancyInformation.isTheNeutralElement - case _ => false + case (_, FinalIPResult(sci)) => sci.isTheNeutralElement + case _ => false } 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.appendToFpe2Sci(next._1, next._2.asFinal.p.stringConstancyInformation) - } + finalResultsWithoutNeutralElements.foreach { next => state.appendToFpe2Sci(next._1, next._2.asFinal.sci) } if (intermediateResults.isEmpty) { finalResults.map(_._2).toList @@ -258,9 +247,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( * 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] - )(implicit state: State): EOptionP[Entity, StringConstancyProperty] = { + private def valueOfAppendCall(call: T)(implicit state: State): IPResult = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar val defSites = param.definedBy.toArray.sorted @@ -268,10 +255,10 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( // Defer the computation if there is at least one intermediate result if (values.exists(_.isRefinable)) { - return values.find(_.isRefinable).get + return InterimIPResult.lb } - val sciValues = values.map { _.asFinal.p.stringConstancyInformation } + val sciValues = values.map { _.asFinal.sci } 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 val newValueSci = if (defSitesValueSci.isTheNeutralElement) { @@ -280,9 +267,9 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( } else { val ds = state.tac.stmts(defSites.head).asAssignment.targetVar.usedBy.toArray.min exprHandler.processDefSite(ds) match { - case FinalP(p) => p.stringConstancyInformation + case FinalIPResult(sci) => sci // Defer the computation if there is no final result yet - case interimEP => return interimEP + case _ => return InterimIPResult.lb } } } else { @@ -321,7 +308,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( val e: Integer = defSites.head state.appendToFpe2Sci(pcOfDefSite(e)(state.tac.stmts), newValueSci, reset = true) - FinalEP(e, StringConstancyProperty(finalSci)) + FinalIPResult(finalSci) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala deleted file mode 100644 index 4035148ece..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -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.fpcf.FinalEP - -/** - * Responsible for processing [[VirtualMethodCall]]s in an interprocedural fashion. - * For supported method calls, see the documentation of the `interpret` function. - * - * @author Patrick Mell - */ -case class L1VirtualMethodCallInterpreter[State <: L1ComputationState[State]]() extends L1StringInterpreter[State] { - - 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. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): FinalEP[T, StringConstancyProperty] = { - val sci = instr.name match { - case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) - case _ => StringConstancyInformation.getNeutralElement - } - 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 f66ab0790c..d21355bf5d 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 @@ -16,8 +16,6 @@ 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.fpcf.FinalP -import org.opalj.fpcf.InterimUBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -47,9 +45,9 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.pc)) } else { val sciToAdd = interpretationHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { - case FinalP(p) => p.stringConstancyInformation - case InterimUBP(ub) => ub.stringConstancyInformation - case _ => StringConstancyInformation.lb + case ValueIPResult(sci) => sci + case NoIPResult => StringConstancyInformation.getNeutralElement + case _ => StringConstancyInformation.lb } fpe2Sci(fpe.pc) = ListBuffer(sciToAdd) 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 0596bcb097..8212e92d9d 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 @@ -8,9 +8,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.Method -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP /** * @author Patrick Mell @@ -38,7 +35,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, StringConstancyProperty]]]] + type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[IPResult]]] /** * This type serves as a lookup mechanism to find out which functions parameters map to which From b3308a3b32bb65a85136a686c2691669d73c963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 14 Feb 2024 22:58:59 +0100 Subject: [PATCH 374/583] Lower upper bound of interpreted instructions --- .../string_analysis/interpretation/StringInterpreter.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/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index d23f1fc594..930c693c83 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringInterpreter[State <: ComputationState[State]] { - type T <: Any + type T <: ASTNode[V] /** * @param instr The instruction that is to be interpreted. It is the responsibility of implementations to make sure From da42432f3f277e9886d095284b783baf8da64d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 14 Feb 2024 23:18:44 +0100 Subject: [PATCH 375/583] Remove unnecessary state handling for virtual function calls --- .../string_analysis/ComputationState.scala | 41 ------------------- .../L0StaticFunctionCallInterpreter.scala | 11 +++-- .../L1InterpretationHandler.scala | 27 ------------ .../L1NonVirtualFunctionCallInterpreter.scala | 2 - .../L1VirtualFunctionCallInterpreter.scala | 16 +++----- 5 files changed, 11 insertions(+), 86 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 2c89966819..defb310334 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -10,7 +10,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.DeclaredMethod -import org.opalj.br.Method import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP @@ -132,18 +131,6 @@ trait ComputationState[State <: ComputationState[State]] { */ val entity2Function: mutable.Map[SContext, 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 `pc` as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] map accordingly, however, only * if `pc` is not yet present or `sci` not present within the list of `pc`. @@ -226,32 +213,4 @@ trait ComputationState[State <: ComputationState[State]] { } var2IndexMapping(entity).append(pc) } - - /** - * 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/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 9fd1d4b285..5a227ca637 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -66,7 +66,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } } - protected def processArbitraryCall(instr: T, defSite: Int)(implicit + private def processArbitraryCall(instr: T, defSite: Int)(implicit state: State ): IPResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) @@ -84,7 +84,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } val m = calleeMethod.value - val (_, calleeTac) = getTACAI(ps, m, state) + val (tacEOptP, calleeTac) = getTACAI(ps, m, state) // Continue only when all parameter information are available val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) @@ -98,9 +98,10 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) } + } else { + state.dependees = tacEOptP :: state.dependees } state.nonFinalFunctionArgs(instr) = params - state.appendToMethodPrep2defSite(m, defSite) return InterimIPResult.lb } @@ -108,7 +109,6 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) if (calleeTac.isDefined) { - state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis val returns = calleeTac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) if (returns.isEmpty) { @@ -134,8 +134,7 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } } } else { - // No TAC => Register dependee and continue - state.appendToMethodPrep2defSite(m, defSite) + state.dependees = tacEOptP :: state.dependees EmptyIPResult } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index f1dbca1522..924b266a4a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -175,33 +175,6 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( defSite: Int )(implicit state: State): IPResult = { val r = new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).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). - val isFinalResult = r.isFinal - if (!isFinalResult && !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) || !isFinalResult) - ) { - processedDefSites.remove(defSite) - } else if (state.nonFinalFunctionArgs.contains(expr) || !isPrepDone) { - processedDefSites.remove(defSite) - } - doInterimResultHandling(r, defSite) r } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 05ceb5ec47..990ef20e47 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -33,7 +33,6 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State val (_, tac) = getTACAI(ps, m, state) if (tac.isDefined) { - state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVars and start the string analysis val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) if (returns.isEmpty) { @@ -59,7 +58,6 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State } } } else { - state.appendToMethodPrep2defSite(m, defSite) EmptyIPResult } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 3829a124b1..35ac1272b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -15,7 +15,6 @@ 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.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler @@ -123,12 +122,10 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( val results = methods.map { nextMethod => val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { - state.methodPrep2defSite.remove(nextMethod) 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 - FinalEP(instr, StringConstancyProperty.lb) + // It might be that a function has no return value, e.g., in case it always throws an exception + FinalIPResult.lb } else { val results = returns.map { ret => val entity = @@ -140,23 +137,22 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( pcOfDefSite(defSite)(state.tac.stmts), r.p.stringConstancyInformation ) - r + FinalIPResult(r.p.stringConstancyInformation) case eps => state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) - eps + InterimIPResult.lb } } results.find(_.isRefinable).getOrElse(results.head) } } else { - state.appendToMethodPrep2defSite(nextMethod, defSite) - EPK(state.entity, StringConstancyProperty.key) + EmptyIPResult } } if (results.forall(_.isFinal)) { - FinalIPResult(results.head.asFinal.p.stringConstancyInformation) + FinalIPResult(results.head.asFinal.sci) } else { InterimIPResult.lb } From dcce8f17eaa80ac62ad85b3a49155c3fcd91c7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 15 Feb 2024 13:01:04 +0100 Subject: [PATCH 376/583] Introduce refinability traits into interpretation results --- .../analyses/string_analysis/IPResult.scala | 27 ++++++++++--------- .../BinaryExprInterpreter.scala | 4 +-- .../DoubleValueInterpreter.scala | 2 +- .../FloatValueInterpreter.scala | 2 +- .../IntegerValueInterpreter.scala | 2 +- .../StringConstInterpreter.scala | 2 +- .../interpretation/StringInterpreter.scala | 8 ++++++ 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala index 944fddf700..a60e0ad265 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala @@ -24,6 +24,16 @@ trait IPResult { def sciOpt: Option[StringConstancyInformation] } +trait NonRefinableIPResult extends IPResult { + override final def isFinal: Boolean = true +} + +trait RefinableIPResult extends IPResult { + override final def isFinal: Boolean = false + + override final def asFinal: FinalIPResult = throw new UnsupportedOperationException() +} + /** * Indicates that the def site is not relevant to the interpretation of the analyses. * @@ -31,11 +41,9 @@ trait IPResult { * Interpreters handling this result type should either convert it to * [[StringConstancyInformation.getNeutralElement]] or preserve it. */ -object NoIPResult extends IPResult { +object NoIPResult extends NonRefinableIPResult { override def isNoResult: Boolean = true - override def isFinal: Boolean = true - override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement) override def sciOpt: Option[StringConstancyInformation] = None } @@ -44,10 +52,7 @@ trait SomeIPResult extends IPResult { override final def isNoResult: Boolean = false } -object EmptyIPResult extends SomeIPResult { - def isFinal = false - override def asFinal: FinalIPResult = throw new UnsupportedOperationException() - +object EmptyIPResult extends RefinableIPResult with SomeIPResult { override def sciOpt: Option[StringConstancyInformation] = None } @@ -61,8 +66,7 @@ object ValueIPResult { def unapply(valueIPResult: ValueIPResult): Some[StringConstancyInformation] = Some(valueIPResult.sci) } -case class FinalIPResult(override val sci: StringConstancyInformation) extends ValueIPResult { - override final def isFinal: Boolean = true +case class FinalIPResult(override val sci: StringConstancyInformation) extends NonRefinableIPResult with ValueIPResult { override def asFinal: FinalIPResult = this } @@ -71,10 +75,7 @@ object FinalIPResult { def lb = new FinalIPResult(StringConstancyInformation.lb) } -case class InterimIPResult(override val sci: StringConstancyInformation) extends ValueIPResult { - override final def isFinal = false - override def asFinal: FinalIPResult = throw new UnsupportedOperationException() -} +case class InterimIPResult(override val sci: StringConstancyInformation) extends RefinableIPResult with ValueIPResult object InterimIPResult { def lb = new InterimIPResult(StringConstancyInformation.lb) 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 b1e8df79b2..4f0a406d5a 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 @@ -12,7 +12,7 @@ import org.opalj.br.ComputationalTypeInt /** * @author Maximilian Rüsch */ -case class BinaryExprInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { +case class BinaryExprInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { override type T = BinaryExpr[V] @@ -24,7 +24,7 @@ case class BinaryExprInterpreter[State <: ComputationState[State]]() extends Str * * For all other expressions, a [[NoIPResult]] will be returned. */ - def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult = { instr.cTpe match { case ComputationalTypeInt => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicInt) case ComputationalTypeFloat => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicFloat) 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 index 76a1b379c8..411753c391 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/DoubleValueInterpreter.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class DoubleValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { +case class DoubleValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { override type T = DoubleConst 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 index 4ebbeb7374..d18b66a9ee 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/FloatValueInterpreter.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class FloatValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { +case class FloatValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { override type T = FloatConst 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 94eef50964..280419e6c3 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 @@ -14,7 +14,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class IntegerValueInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { +case class IntegerValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { override type T = IntConst 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 7b8d4cfd69..c2f8d320a2 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 @@ -14,7 +14,7 @@ import org.opalj.tac.StringConst /** * @author Maximilian Rüsch */ -case class StringConstInterpreter[State <: ComputationState[State]]() extends StringInterpreter[State] { +case class StringConstInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { override type T = StringConst diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala index 930c693c83..cd899121f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala @@ -145,6 +145,14 @@ trait StringInterpreter[State <: ComputationState[State]] { }) } +trait SingleStepStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { + + /** + * @inheritdoc + */ + override def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult +} + /** * @author Maximilian Rüsch */ From bdb8855bb1d418275c3a21f6fae6d5de469b2245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 15 Feb 2024 21:43:13 +0100 Subject: [PATCH 377/583] Revamp dependency handling for interpretation --- .../string_analysis/ComputationState.scala | 119 +----- .../string_analysis/EPSDepender.scala | 39 ++ .../analyses/string_analysis/IPResult.scala | 137 ++++++- .../string_analysis/IPResultDepender.scala | 37 ++ .../string_analysis/StringAnalysis.scala | 108 ++--- .../StringInterpreter.scala | 94 ++++- .../BinaryExprInterpreter.scala | 10 +- .../DoubleValueInterpreter.scala | 14 +- .../FloatValueInterpreter.scala | 14 +- .../IntegerValueInterpreter.scala | 14 +- .../InterpretationHandler.scala | 96 +++-- .../StringConstInterpreter.scala | 14 +- .../string_analysis/l0/L0StringAnalysis.scala | 82 ++-- .../L0ArrayAccessInterpreter.scala | 47 +-- .../L0InterpretationHandler.scala | 82 ++-- .../L0NonVirtualMethodCallInterpreter.scala | 39 +- .../L0StaticFunctionCallInterpreter.scala | 177 ++++++--- .../interpretation/L0StringInterpreter.scala | 2 - .../L0VirtualFunctionCallInterpreter.scala | 297 ++++++++------ .../L0VirtualMethodCallInterpreter.scala | 15 +- .../l1/L1ComputationState.scala | 4 + .../string_analysis/l1/L1StringAnalysis.scala | 70 ++-- .../l1/finalizer/ArrayLoadFinalizer.scala | 40 -- .../l1/finalizer/FieldReadFinalizer.scala | 26 -- .../l1/finalizer/L1Finalizer.scala | 35 -- .../l1/finalizer/NewArrayFinalizer.scala | 25 -- .../NonVirtualMethodCallFinalizer.scala | 38 -- .../StaticFunctionCallFinalizer.scala | 43 -- .../VirtualFunctionCallFinalizer.scala | 105 ----- .../L1FieldReadInterpreter.scala | 27 +- .../L1InterpretationHandler.scala | 343 +++------------- .../L1NewArrayInterpreter.scala | 67 ++-- .../L1NonVirtualFunctionCallInterpreter.scala | 37 +- .../interpretation/L1StringInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 371 +++++++----------- .../preprocessing/PathTransformer.scala | 71 +--- 36 files changed, 1148 insertions(+), 1593 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{interpretation => }/StringInterpreter.scala (71%) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index defb310334..0d91b67ece 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -9,13 +9,16 @@ package string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.br.DeclaredMethod +import org.opalj.br.DefinedMethod +import org.opalj.br.Method import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property +import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.properties.TACAI /** * This class is to be used to store state information that are required at a later point in @@ -23,7 +26,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path * have all required information ready for a final result. */ trait ComputationState[State <: ComputationState[State]] { - val dm: DeclaredMethod + val dm: DefinedMethod /** * The entity for which the analysis was started with. @@ -33,54 +36,37 @@ trait ComputationState[State <: ComputationState[State]] { /** * The Three-Address Code of the entity's method */ - var tac: TAC = _ // TODO add dm to tac dependee mapping + var tac: TAC = _ /** * The interpretation handler to use for computing a final result (if possible). */ var iHandler: InterpretationHandler[State] = _ - /** - * 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: InterpretationHandler[State] = _ - /** * The computed lean path that corresponds to the given entity */ var computedLeanPath: Path = _ + var tacDependee: Option[EOptionP[Method, TACAI]] = _ + + val fpe2EPSDependees: mutable.Map[Int, List[(EOptionP[Entity, Property], SomeEPS => IPResult)]] = mutable.Map() + val fpe2iprDependees: mutable.Map[Int, (List[IPResult], IPResult => IPResult)] = mutable.Map() + /** * If not empty, this routine can only produce an intermediate result */ var dependees: List[EOptionP[Entity, Property]] = List() - var dependeeDefSites: List[IPResult] = List() - /** * A mapping from DUVar elements to the corresponding values of the FlatPathElements */ val var2IndexMapping: mutable.Map[SEntity, ListBuffer[Int]] = mutable.Map() /** - * A mapping from values of FlatPathElements to StringConstancyInformation - */ - val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() - - /** - * A mapping from a value of a FlatPathElement to StringConstancyInformation which are not yet final. + * A mapping from values of FlatPathElements to an interpretation result represented by an [[IPResult]] */ - 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[SEntity, StringConstancyInformation] = - mutable.Map() + val fpe2ipr: mutable.Map[Int, IPResult] = mutable.Map() /** * An analysis may depend on the evaluation of its parameters. This number indicates how many @@ -88,12 +74,6 @@ trait ComputationState[State <: ComputationState[State]] { */ 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 @@ -131,79 +111,6 @@ trait ComputationState[State <: ComputationState[State]] { */ val entity2Function: mutable.Map[SContext, ListBuffer[FunctionCall[V]]] = mutable.Map() - /** - * Takes a `pc` as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] map accordingly, however, only - * if `pc` is not yet present or `sci` not present within the list of `pc`. - */ - def appendToFpe2Sci( - pc: Int, - sci: StringConstancyInformation, - reset: Boolean = false - ): Unit = { - if (reset || !fpe2sci.contains(pc)) { - fpe2sci(pc) = ListBuffer() - } - if (!fpe2sci(pc).contains(sci)) { - fpe2sci(pc).append(sci) - } - } - - /** - * 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 pc 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( - pc: Int, - sci: StringConstancyInformation, - entity: Option[SEntity] = None - ): Unit = { - val numElements = var2IndexMapping.values.flatten.count(_ == pc) - var addedNewList = false - if (!interimFpe2sci.contains(pc)) { - interimFpe2sci(pc) = ListBuffer() - addedNewList = true - } - // Append an element - val containsSci = interimFpe2sci(pc).contains(sci) - if (!containsSci && entity.isEmpty) { - if (!addedNewList && interimFpe2sci(pc).length == numElements) { - interimFpe2sci(pc).remove(0) - } - interimFpe2sci(pc).append(sci) - } else if (!containsSci && entity.nonEmpty) { - if (!entity2lastInterimFpe2SciValue.contains(entity.get) || - entity2lastInterimFpe2SciValue(entity.get) == StringConstancyInformation.lb - ) { - entity2lastInterimFpe2SciValue(entity.get) = sci - if (interimFpe2sci(pc).nonEmpty) { - interimFpe2sci(pc).remove(0) - } - interimFpe2sci(pc).append(sci) - } - } - } - /** * Takes an entity as well as a pc and append it to [[var2IndexMapping]]. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala new file mode 100644 index 0000000000..4497caf573 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis + +import org.opalj.fpcf.SomeEOptionP + +/** + * @author Maximilian Rüsch + */ +trait EPSDepender[T <: ASTNode[V], State <: ComputationState[State]] { + type Self <: EPSDepender[T, State] + + def instr: T + def pc: Int + def state: State + def dependees: Seq[SomeEOptionP] + + def withDependees(newDependees: Seq[SomeEOptionP]): Self +} + +private[string_analysis] case class SimpleEPSDepender[T <: ASTNode[V], State <: ComputationState[State]]( + override val instr: T, + override val pc: Int, + override val state: State, + override val dependees: Seq[SomeEOptionP] +) extends EPSDepender[T, State] { + + type Self = SimpleEPSDepender[T, State] + + override def withDependees(newDependees: Seq[SomeEOptionP]): SimpleEPSDepender[T, State] = SimpleEPSDepender( + instr, + pc, + state, + newDependees + ) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala index a60e0ad265..6b047be8bc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala @@ -5,7 +5,10 @@ package fpcf package analyses package string_analysis +import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPS /** * A result of an interpretation of some def site using some [[interpretation.InterpretationHandler]]. @@ -13,50 +16,75 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation * * @author Maximilian Rüsch */ -trait IPResult { +sealed trait IPResult { def isNoResult: Boolean + def isFallThroughResult: Boolean def isFinal: Boolean final def isRefinable: Boolean = !isFinal def asFinal: FinalIPResult + def asNonRefinable: NonRefinableIPResult + def asRefinable: RefinableIPResult + + def e: (DefinedMethod, Int) = (method, pc) + def method: DefinedMethod + def pc: Int def sciOpt: Option[StringConstancyInformation] } -trait NonRefinableIPResult extends IPResult { +object IPResult { + def unapply(ipResult: IPResult): Some[(DefinedMethod, Int)] = Some((ipResult.method, ipResult.pc)) +} + +sealed trait NonRefinableIPResult extends IPResult { override final def isFinal: Boolean = true + override def asNonRefinable: NonRefinableIPResult = this + override def asRefinable: RefinableIPResult = throw new UnsupportedOperationException() } -trait RefinableIPResult extends IPResult { +sealed trait RefinableIPResult extends IPResult { override final def isFinal: Boolean = false override final def asFinal: FinalIPResult = throw new UnsupportedOperationException() + override def asNonRefinable: NonRefinableIPResult = throw new UnsupportedOperationException() + override def asRefinable: RefinableIPResult = this } /** - * Indicates that the def site is not relevant to the interpretation of the analyses. + * Indicates that the pc is not relevant to the interpretation of the analyses. * - * @note Since the def site is not relevant, we are able to return a final result with the neutral element when needed. + * @note Since the pc is not relevant, we are able to return a final result with the neutral element when needed. * Interpreters handling this result type should either convert it to * [[StringConstancyInformation.getNeutralElement]] or preserve it. */ -object NoIPResult extends NonRefinableIPResult { +case class NoIPResult(method: DefinedMethod, pc: Int) extends NonRefinableIPResult { + override def isNoResult: Boolean = true + override def isFallThroughResult: Boolean = false + + override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement, method, pc) + override def sciOpt: Option[StringConstancyInformation] = None +} + +case class FallThroughIPResult(method: DefinedMethod, pc: Int, fallThroughPC: Int) extends NonRefinableIPResult { override def isNoResult: Boolean = true + override def isFallThroughResult: Boolean = true - override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement) + override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement, method, pc) override def sciOpt: Option[StringConstancyInformation] = None } -trait SomeIPResult extends IPResult { +sealed trait SomeIPResult extends IPResult { override final def isNoResult: Boolean = false + override final def isFallThroughResult: Boolean = false } -object EmptyIPResult extends RefinableIPResult with SomeIPResult { +case class EmptyIPResult(method: DefinedMethod, pc: Int) extends RefinableIPResult with SomeIPResult { override def sciOpt: Option[StringConstancyInformation] = None } -trait ValueIPResult extends SomeIPResult { +sealed trait ValueIPResult extends SomeIPResult { override final def sciOpt: Option[StringConstancyInformation] = Some(sci) def sci: StringConstancyInformation @@ -66,17 +94,96 @@ object ValueIPResult { def unapply(valueIPResult: ValueIPResult): Some[StringConstancyInformation] = Some(valueIPResult.sci) } -case class FinalIPResult(override val sci: StringConstancyInformation) extends NonRefinableIPResult with ValueIPResult { +case class FinalIPResult( + override val sci: StringConstancyInformation, + method: DefinedMethod, + pc: Int +) extends NonRefinableIPResult with ValueIPResult { override def asFinal: FinalIPResult = this } object FinalIPResult { - def nullElement = new FinalIPResult(StringConstancyInformation.getNullElement) - def lb = new FinalIPResult(StringConstancyInformation.lb) + def nullElement(method: DefinedMethod, pc: Int) = + new FinalIPResult(StringConstancyInformation.getNullElement, method, pc) + def lb(method: DefinedMethod, pc: Int) = new FinalIPResult(StringConstancyInformation.lb, method, pc) } -case class InterimIPResult(override val sci: StringConstancyInformation) extends RefinableIPResult with ValueIPResult +case class InterimIPResult private ( + override val sci: StringConstancyInformation, + override val method: DefinedMethod, + override val pc: Int, + ipResultDependees: Iterable[RefinableIPResult] = List.empty, + ipResultContinuation: Option[IPResult => IPResult] = None, + epsDependees: Iterable[SomeEOptionP] = List.empty, + epsContinuation: Option[SomeEPS => IPResult] = None +) extends RefinableIPResult with ValueIPResult object InterimIPResult { - def lb = new InterimIPResult(StringConstancyInformation.lb) + def fromRefinableIPResults( + sci: StringConstancyInformation, + method: DefinedMethod, + pc: Int, + refinableResults: Iterable[RefinableIPResult], + ipResultContinuation: IPResult => IPResult + ) = new InterimIPResult(sci, method, pc, refinableResults, Some(ipResultContinuation)) + + def lbWithIPResultDependees( + method: DefinedMethod, + pc: Int, + refinableResults: Iterable[RefinableIPResult], + ipResultContinuation: IPResult => IPResult + ) = new InterimIPResult(StringConstancyInformation.lb, method, pc, refinableResults, Some(ipResultContinuation)) + + def fromRefinableEPSResults( + sci: StringConstancyInformation, + method: DefinedMethod, + pc: Int, + epsDependees: Iterable[SomeEOptionP], + epsContinuation: SomeEPS => IPResult + ) = new InterimIPResult( + sci, + method, + pc, + epsDependees = epsDependees, + epsContinuation = Some(epsContinuation) + ) + + def lbWithEPSDependees( + method: DefinedMethod, + pc: Int, + epsDependees: Iterable[SomeEOptionP], + epsContinuation: SomeEPS => IPResult + ) = new InterimIPResult( + StringConstancyInformation.lb, + method, + pc, + epsDependees = epsDependees, + epsContinuation = Some(epsContinuation) + ) + + def unapply(interimIPResult: InterimIPResult): Some[( + StringConstancyInformation, + DefinedMethod, + Int, + Iterable[_], + (_) => IPResult + )] = { + if (interimIPResult.ipResultContinuation.isDefined) { + Some(( + interimIPResult.sci, + interimIPResult.method, + interimIPResult.pc, + interimIPResult.ipResultDependees, + interimIPResult.ipResultContinuation.get + )) + } else { + Some(( + interimIPResult.sci, + interimIPResult.method, + interimIPResult.pc, + interimIPResult.epsDependees, + interimIPResult.epsContinuation.get + )) + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala new file mode 100644 index 0000000000..b3dcc6e8e7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis + +/** + * @author Maximilian Rüsch + */ +trait IPResultDepender[T <: ASTNode[V], State <: ComputationState[State]] { + type Self <: IPResultDepender[T, State] + + def instr: T + def pc: Int + def state: State + def dependees: Seq[IPResult] + + def withDependees(newDependees: Seq[IPResult]): Self +} + +private[string_analysis] case class SimpleIPResultDepender[T <: ASTNode[V], State <: ComputationState[State]]( + override val instr: T, + override val pc: Int, + override val state: State, + override val dependees: Seq[IPResult] +) extends IPResultDepender[T, State] { + + type Self = SimpleIPResultDepender[T, State] + + override def withDependees(newDependees: Seq[IPResult]): SimpleIPResultDepender[T, State] = SimpleIPResultDepender( + instr, + pc, + state, + newDependees + ) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 744979c7c8..84436072a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -9,6 +9,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.FieldType +import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.fpcf.FPCFAnalysis @@ -23,6 +24,8 @@ import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.log.OPALLogger +import org.opalj.log.Warn import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -44,13 +47,9 @@ trait StringAnalysis extends FPCFAnalysis { val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - /** - * Returns the current interim result for the given state. If required, custom lower and upper bounds can be used - * for the interim result. - */ protected def getInterimResult(state: State): InterimResult[StringConstancyProperty] = InterimResult( state.entity, - computeNewLowerBound(state), + StringConstancyProperty.lb, computeNewUpperBound(state), state.dependees.toSet, continuation(state) @@ -58,17 +57,16 @@ trait StringAnalysis extends FPCFAnalysis { private def computeNewUpperBound(state: State): StringConstancyProperty = { if (state.computedLeanPath != null) { - StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( - state.computedLeanPath, - state.interimFpe2sci - )(state).reduce(true)) + StringConstancyProperty( + PathTransformer + .pathToStringTree(state.computedLeanPath)(state) + .reduce(true) + ) } else { StringConstancyProperty.lb } } - private def computeNewLowerBound(state: State): StringConstancyProperty = StringConstancyProperty.lb - /** * 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 @@ -86,24 +84,20 @@ trait StringAnalysis extends FPCFAnalysis { * be returned. */ protected[this] def continuation(state: State)(eps: SomeEPS): ProperPropertyComputationResult = { - state.dependees = state.dependees.filter(_.e != eps.e) - eps match { - case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) => - // 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 - } + case FinalP(tac: TACAI) if + eps.pk.equals(TACAI.key) && + state.tacDependee.isDefined && + state.tacDependee.get == eps => + state.tac = tac.tac.get + state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) determinePossibleStrings(state) + case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => + state.dependees = state.dependees.filter(_.e != eps.e) + val e = entity.asInstanceOf[SContext] - // For updating the interim state - state.var2IndexMapping(eps.e.asInstanceOf[SContext]._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 necessary, update the parameter information with which the surrounding function / method of the entity was called with if (state.paramResultPositions.contains(e)) { val pos = state.paramResultPositions(e) state.params(pos._1)(pos._2) = p.stringConstancyInformation @@ -113,15 +107,11 @@ trait StringAnalysis extends FPCFAnalysis { // If necessary, update parameter information of function calls if (state.entity2Function.contains(e)) { - state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( - _, - p.stringConstancyInformation - )) // Update the state state.entity2Function(e).foreach { f => val pos = state.nonFinalFunctionArgsPos(f)(e) state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = - FinalIPResult(p.stringConstancyInformation) + FinalIPResult(p.stringConstancyInformation, declaredMethods(e._2), pos._3) // Housekeeping val index = state.entity2Function(e).indexOf(f) state.entity2Function(e).remove(index) @@ -145,8 +135,8 @@ trait StringAnalysis extends FPCFAnalysis { } } - if (state.isSetupCompleted && state.parameterDependeesCount == 0) { - processFinalP(state, eps.e, p) + if (state.parameterDependeesCount == 0) { + state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run if (state.dependees.isEmpty) { computeFinalResult(state) @@ -158,29 +148,12 @@ trait StringAnalysis extends FPCFAnalysis { } case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = eps :: state.dependees - val puVar = eps.e.asInstanceOf[SContext]._1 - state.var2IndexMapping(puVar).foreach { i => - state.appendToInterimFpe2Sci( - i, - ub.stringConstancyInformation, - Some(puVar) - ) - } getInterimResult(state) case _ => - state.dependees = eps :: state.dependees getInterimResult(state) - } } - protected[string_analysis] def finalizePreparations( - path: Path, - state: State, - iHandler: InterpretationHandler[State] - ): Unit = {} - /** * 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, @@ -194,46 +167,35 @@ trait StringAnalysis extends FPCFAnalysis { * @return Returns the final result. */ protected def computeFinalResult(state: State): Result = { - finalizePreparations(state.computedLeanPath, state, state.iHandler) - val finalSci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, - state.fpe2sci, - resetExprHandler = false - )(state).reduce(true) + val finalSci = PathTransformer + .pathToStringTree(state.computedLeanPath)(state) + .reduce(true) + if (state.fpe2iprDependees.nonEmpty) { + OPALLogger.logOnce(Warn( + "string analysis", + "The state still contains IPResult dependees after all EPS dependees were resolved!" + )) + } StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(finalSci)) } - protected def processFinalP( - state: State, - e: Entity, - p: StringConstancyProperty - ): Unit = { - // Add mapping information (which will be used for computing the final result) - state.var2IndexMapping(e.asInstanceOf[SContext]._1).foreach { - state.appendToFpe2Sci(_, p.stringConstancyInformation) - } - - state.dependees = state.dependees.filter(_.e != e) - } - /** * 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 state The current state of the computation. This function will alter [[ComputationState.fpe2sci]]. + * @param state The current state of the computation. This function will alter [[ComputationState.fpe2ipr]]. * @return Returns `true` if all values computed for the path are final results. */ protected def computeResultsForPath(p: Path)(implicit state: State): Boolean = { var hasFinalResult = true p.elements.foreach { case fpe: FlatPathElement => - if (!state.fpe2sci.contains(fpe.pc)) { + if (!state.fpe2ipr.contains(fpe.pc) || state.fpe2ipr(fpe.pc).isRefinable) { val r = state.iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) - if (r.isFinal) { - state.appendToFpe2Sci(fpe.pc, r.asFinal.sci, reset = true) - } else { + state.fpe2ipr(fpe.pc) = r + if (r.isRefinable) { hasFinalResult = false } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala similarity index 71% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index cd899121f4..8b03b5f6e5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -4,15 +4,18 @@ package tac package fpcf package analyses package string_analysis -package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.Method import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.SomeFinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI /** @@ -60,9 +63,6 @@ trait StringInterpreter[State <: ComputationState[State]] { if (tacai.hasUBP) { (tacai, tacai.ub.tac) } else { - if (tacai.isRefinable) { - s.dependees = tacai :: s.dependees - } (tacai, None) } } @@ -156,23 +156,75 @@ trait SingleStepStringInterpreter[State <: ComputationState[State]] extends Stri /** * @author Maximilian Rüsch */ -trait DependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { - - protected def handleDependentDefSite(defSite: Int)(implicit - state: State, - exprHandler: InterpretationHandler[State] - ): IPResult = { - exprHandler.processDefSite(defSite) match { - case ipr: FinalIPResult => - state.dependeeDefSites = state.dependeeDefSites.filter(_ != ipr) - ipr - case ipr: InterimIPResult => - if (!state.dependeeDefSites.contains(ipr)) { - state.dependeeDefSites = ipr :: state.dependeeDefSites - } - ipr - case ipr => - ipr +trait IPResultDependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { + + protected final def awaitAllFinalContinuation( + depender: IPResultDepender[T, State], + finalResult: Iterable[IPResult] => IPResult + )(result: IPResult): IPResult = { + if (result.isFinal) { + val updatedDependees = depender.dependees.updated( + depender.dependees.indexWhere(_.e == result.e), + result + ) + + if (updatedDependees.forall(_.isFinal)) { + finalResult(updatedDependees) + } else { + InterimIPResult.fromRefinableIPResults( + StringConstancyInformation.lb, + depender.state.dm, + depender.pc, + updatedDependees.filter(_.isRefinable).asInstanceOf[Seq[RefinableIPResult]], + awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) + ) + } + } else { + InterimIPResult.fromRefinableIPResults( + StringConstancyInformation.lb, + depender.state.dm, + depender.pc, + depender.dependees.asInstanceOf[Seq[RefinableIPResult]], + awaitAllFinalContinuation(depender, finalResult) + ) + } + } +} + +/** + * @author Maximilian Rüsch + */ +trait EPSDependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { + + protected final def awaitAllFinalContinuation( + depender: EPSDepender[T, State], + finalResult: Iterable[SomeFinalEP] => IPResult + )(result: SomeEPS): IPResult = { + if (result.isFinal) { + val updatedDependees = depender.dependees.updated( + depender.dependees.indexWhere(_.e.asInstanceOf[Entity] == result.e.asInstanceOf[Entity]), + result + ) + + if (updatedDependees.forall(_.isFinal)) { + finalResult(updatedDependees.asInstanceOf[Iterable[SomeFinalEP]]) + } else { + InterimIPResult.fromRefinableEPSResults( + StringConstancyInformation.lb, + depender.state.dm, + depender.pc, + updatedDependees.filter(_.isRefinable), + awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) + ) + } + } else { + InterimIPResult.fromRefinableEPSResults( + StringConstancyInformation.lb, + depender.state.dm, + depender.pc, + depender.dependees, + awaitAllFinalContinuation(depender, finalResult) + ) } } } 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 4f0a406d5a..ff21412ed5 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 @@ -25,10 +25,14 @@ case class BinaryExprInterpreter[State <: ComputationState[State]]() extends Sin * For all other expressions, a [[NoIPResult]] will be returned. */ def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult = { + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) instr.cTpe match { - case ComputationalTypeInt => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicInt) - case ComputationalTypeFloat => FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicFloat) - case _ => NoIPResult + case ComputationalTypeInt => + FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicInt, state.dm, defSitePC) + case ComputationalTypeFloat => + FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicFloat, state.dm, defSitePC) + case _ => + NoIPResult(state.dm, defSitePC) } } } 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 index 411753c391..c7951211da 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/DoubleValueInterpreter.scala @@ -18,11 +18,15 @@ case class DoubleValueInterpreter[State <: ComputationState[State]]() extends Si override type T = DoubleConst def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + ), + state.dm, + pcOfDefSite(defSite)(state.tac.stmts) + ) } object DoubleValueInterpreter { 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 index d18b66a9ee..8a1f0dfe1e 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/FloatValueInterpreter.scala @@ -18,11 +18,15 @@ case class FloatValueInterpreter[State <: ComputationState[State]]() extends Sin override type T = FloatConst def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + ), + state.dm, + pcOfDefSite(defSite)(state.tac.stmts) + ) } object FloatValueInterpreter { 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 280419e6c3..88b69ee821 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 @@ -19,11 +19,15 @@ case class IntegerValueInterpreter[State <: ComputationState[State]]() extends S override type T = IntConst def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - )) + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + ), + state.dm, + pcOfDefSite(defSite)(state.tac.stmts) + ) } object IntegerValueInterpreter { 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 57e787548d..9bd9b14543 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 @@ -9,19 +9,23 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.ObjectType 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 +/** + * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site + * might have. + * + * [[InterpretationHandler]]s of any level may use [[StringInterpreter]]s from their level or any level below. + * [[SingleStepStringInterpreter]]s defined in the [[interpretation]] package may be used by any level. + * + * @author Maximilian Rüsch + */ abstract class InterpretationHandler[State <: ComputationState[State]] { - /** - * A list of definition sites that have already been processed. Store it as a map for constant - * look-ups (the value is not relevant and thus set to [[Unit]]). - */ - protected val processedDefSites: mutable.Map[Int, Unit] = mutable.Map() - /** * Processes a given definition site. That is, this function determines the interpretation of * the specified instruction. @@ -37,24 +41,67 @@ abstract class InterpretationHandler[State <: ComputationState[State]] { * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). * The entity of the result will be the given `defSite`. */ - def processDefSite(defSite: Int)(implicit state: State): IPResult + def processDefSite(defSite: Int)(implicit state: State): IPResult = { + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - /** - * [[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, another instance of an implementation of [[InterpretationHandler]] could be - * instantiated.) - */ - def reset(): Unit = { - processedDefSites.clear() + if (state.fpe2ipr.contains(defSitePC) && state.fpe2ipr(defSitePC).isFinal) { + if (state.fpe2ipr(defSitePC).isFallThroughResult) { + return processDefSite(valueOriginOfPC( + state.fpe2ipr(defSitePC).asInstanceOf[FallThroughIPResult].fallThroughPC, + state.tac.pcToIndex + ).get) + } else { + return state.fpe2ipr(defSitePC) + } + } + + if (defSite < 0) { + val params = state.params.toList.map(_.toList) + if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { + state.fpe2ipr(defSitePC) = FinalIPResult.lb(state.dm, defSitePC) + return FinalIPResult.lb(state.dm, defSitePC) + } else { + val sci = getParam(params, defSite) + state.fpe2ipr(defSitePC) = FinalIPResult(sci, state.dm, defSitePC) + return FinalIPResult(sci, state.dm, defSitePC) + } + } + + if (state.fpe2iprDependees.contains(defSitePC)) { + val oldDependees = state.fpe2iprDependees(defSitePC) + val updatedDependees = oldDependees._1.map { + case ripr: RefinableIPResult => processDefSite(valueOriginOfPC(ripr.pc, state.tac.pcToIndex).get) + case ipr => ipr + } + if (updatedDependees == oldDependees._1) { + state.fpe2ipr(defSitePC) + } else { + state.fpe2iprDependees(defSitePC) = (updatedDependees, oldDependees._2) + var newResult = state.fpe2ipr(defSitePC) + for { + ipr <- updatedDependees + if !oldDependees._1.contains(ipr) + } { + newResult = oldDependees._2(ipr) + } + newResult + } + } else { + val result = processNewDefSite(defSite) + state.fpe2ipr(defSitePC) = result + + if (result.isFallThroughResult) { + processDefSite(valueOriginOfPC( + result.asInstanceOf[FallThroughIPResult].fallThroughPC, + state.tac.pcToIndex + ).get) + } else { + result + } + } } - /** - * Finalized a given definition state. - */ - def finalizeDefSite(defSite: Int)(implicit state: State): Unit = {} + protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult /** * This function takes parameters and a definition site and extracts the desired parameter from @@ -112,10 +159,9 @@ object InterpretationHandler { */ 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 VirtualFunctionCall(_, clazz, _, "append", _, _, _) => + clazz.mostPreciseObjectType == ObjectType.StringBuilder || + clazz.mostPreciseObjectType == ObjectType.StringBuffer case _ => false } } 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 c2f8d320a2..61d1db20f1 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 @@ -19,11 +19,15 @@ case class StringConstInterpreter[State <: ComputationState[State]]() extends Si override type T = StringConst def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value - )) + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value + ), + state.dm, + pcOfDefSite(defSite)(state.tac.stmts) + ) } object StringConstInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 1b5989d149..86b3ab1805 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -6,25 +6,20 @@ package analyses package string_analysis package l0 -import org.opalj.br.DeclaredMethod +import org.opalj.br.DefinedMethod import org.opalj.br.analyses.ProjectInformationKeys 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.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimLUBP -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI @@ -45,7 +40,8 @@ trait L0ComputationState[State <: L0ComputationState[State]] extends Computation * 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]]. + * values. Note that all this is handled by + * [[org.opalj.br.fpcf.properties.string_definition.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 @@ -59,26 +55,28 @@ trait L0ComputationState[State <: L0ComputationState[State]] extends Computation class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { protected[l0] case class CState( - override val dm: DeclaredMethod, + override val dm: DefinedMethod, override val entity: SContext ) extends L0ComputationState[CState] override type State = CState def analyze(data: SContext): ProperPropertyComputationResult = { - // Retrieve TAC from property store - val tacOpt: Option[TAC] = ps(data._2, TACAI.key) match { - case UBP(tac) => if (tac.tac.isEmpty) None else Some(tac.tac.get) - case _ => None - } - // No TAC available, e.g., because the method has no body - if (tacOpt.isEmpty) - return Result(data, StringConstancyProperty.lb) // TODO add continuation - val state = CState(declaredMethods(data._2), data) state.iHandler = L0InterpretationHandler() - state.interimIHandler = L0InterpretationHandler() - state.tac = tacOpt.get + + val tacaiEOptP = ps(data._2, TACAI.key) + if (tacaiEOptP.isRefinable) { + state.tacDependee = Some(tacaiEOptP) + return getInterimResult(state) + } + + if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + return Result(state.entity, StringConstancyProperty.lb) + } + + state.tac = tacaiEOptP.ub.tac.get determinePossibleStrings(state) } @@ -86,7 +84,6 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis state: State ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac - val stmts = tac.stmts val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(tac.pcToIndex) @@ -111,8 +108,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis if (state.parameterDependeesCount > 0) { return getInterimResult(state) - } else { - state.isSetupCompleted = true + } + + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uVar) } // Interpret a function / method parameter using the parameter information in state @@ -121,14 +120,8 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) } - val expr = stmts(defSites.head).asAssignment.expr + val expr = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - val leanPath = computeLeanPathForStringBuilder(uVar) - if (leanPath.isEmpty) { - return Result(state.entity, StringConstancyProperty.lb) - } - state.computedLeanPath = leanPath.get - // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, puVar) if (dependentVars.nonEmpty) { @@ -137,32 +130,19 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { case FinalEP(e, p) => // Add mapping information (which will be used for computing the final result) - state.var2IndexMapping(e._1).foreach { - state.appendToFpe2Sci(_, p.stringConstancyInformation) - } + // state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci(_, p.stringConstancyInformation)) state.dependees = state.dependees.filter(_.e.asInstanceOf[SContext] != e) case ep => state.dependees = ep :: state.dependees } } } + } - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } + if (state.dependees.isEmpty) { + computeFinalResult(state) } else { - // We deal with pure strings TODO unify result handling - val sci = StringConstancyInformation.reduceMultiple( - uVar.definedBy.toArray.sorted.map { ds => state.iHandler.processDefSite(ds).asFinal.sci } - ) - - if (state.dependees.isEmpty) { - Result(state.entity, StringConstancyProperty(sci)) - } else { - getInterimResult(state) - } + getInterimResult(state) } } @@ -177,17 +157,14 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis state: State )(eps: SomeEPS): ProperPropertyComputationResult = eps match { case FinalEP(e: Entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - processFinalP(state, e, p) + state.dependees = state.dependees.filter(_.e != e) if (state.dependees.isEmpty) { computeFinalResult(state) } else { getInterimResult(state) } - - case InterimLUBP(lb, ub) => - InterimResult(state.entity, lb, ub, state.dependees.toSet, continuation(state)) case _ => - throw new IllegalStateException("Could not process the continuation successfully.") + super.continuation(state)(eps) } } @@ -197,7 +174,6 @@ sealed trait L0StringAnalysisScheduler extends FPCFAnalysisScheduler { override final def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), - PropertyBounds.ub(Callees), PropertyBounds.lub(StringConstancyProperty) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index b81dcc4429..a3f000611d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -9,7 +9,6 @@ package interpretation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.collection.immutable.IntTrieSet -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -19,44 +18,48 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { +) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { override type T = ArrayLoad[V] override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { implicit val stmts: Array[Stmt[V]] = state.tac.stmts - val allDefSitesByPC = - L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr).map(ds => (pcOfDefSite(ds), ds)).toMap - val results = allDefSitesByPC.keys.toList.sorted.map { pc => - (pc, handleDependentDefSite(allDefSitesByPC(pc))(state, exprHandler)) - }.map { - case (pc, result) => - if (result.isFinal) - state.appendToFpe2Sci(pc, result.asFinal.sci) - result - } + val defSitePCs = L0ArrayAccessInterpreter.getStoreAndLoadDefSitePCs(instr) + val results = defSitePCs.map { pc => exprHandler.processDefSite(valueOriginOfPC(pc, state.tac.pcToIndex).get) } // Add information of parameters // TODO dont we have to incorporate parameter information into the scis? instr.arrayRef.asVar.toPersistentForm.defPCs.filter(_ < 0).foreach { pc => val paramPos = Math.abs(pc + 2) val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) - state.appendToFpe2Sci(pc, sci) + val r = FinalIPResult(sci, state.dm, pc) + state.fpe2ipr(pc) = r } val unfinishedDependees = results.exists(_.isRefinable) if (unfinishedDependees) { - InterimIPResult.lb + InterimIPResult.lbWithIPResultDependees( + state.dm, + pcOfDefSite(defSite), + results.filter(_.isRefinable).map(_.asRefinable), + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, pcOfDefSite(defSite), state, results), + finalResult(pcOfDefSite(defSite)) + ) + ) } else { - var resultSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - if (resultSci.isTheNeutralElement) { - resultSci = StringConstancyInformation.lb - } + finalResult(pcOfDefSite(defSite))(results) + } + } - state.appendToFpe2Sci(pcOfDefSite(defSite), resultSci) - FinalIPResult(resultSci) + private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { + var resultSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) + if (resultSci.isTheNeutralElement) { + resultSci = StringConstancyInformation.lb } + + FinalIPResult(resultSci, state.dm, pc) } } @@ -69,7 +72,7 @@ object L0ArrayAccessInterpreter { * * @return All definition sites associated with the array stores and array loads sorted in ascending order. */ - def getStoreAndLoadDefSites(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { + def getStoreAndLoadDefSitePCs(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { var defSites = IntTrieSet.empty instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { @@ -83,6 +86,6 @@ object L0ArrayAccessInterpreter { } } - defSites.toList.sorted + defSites.toList.map(pcOfDefSite(_)).sorted } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 6a37e8301e..6b3c60e053 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -7,9 +7,7 @@ package string_analysis package l0 package interpretation -import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter @@ -19,12 +17,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter /** - * `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. - *

    - * This handler may use [[L0StringInterpreter]]s and general - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter]]s. + * @inheritdoc * * @author Maximilian Rüsch */ @@ -34,68 +27,37 @@ class L0InterpretationHandler[State <: L0ComputationState[State]]()( ps: PropertyStore ) extends InterpretationHandler[State] { - /** - * Processed the given definition site in an intraprocedural fashion. - *

    - * @inheritdoc - */ - override def processDefSite(defSite: Int)(implicit state: State): IPResult = { + override protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult = { val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - if (defSite < 0) { - val params = state.params.toList.map(_.toList) - if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { - state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.lb) - return FinalIPResult.lb - } else { - val sci = getParam(params, defSite) - state.appendToInterimFpe2Sci(defSitePC, sci) - return FinalIPResult(sci) - } - } else if (processedDefSites.contains(defSite)) { - state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) - return FinalIPResult(StringConstancyInformation.getNeutralElement) - } + state.tac.stmts(defSite) match { + case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite)(state) - processedDefSites(defSite) = () + case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(this).interpret(expr, defSite)(state) + case Assignment(_, _, _: New) => NoIPResult(state.dm, defSitePC) + + // Currently unsupported + case Assignment(_, _, _: GetField[V]) => FinalIPResult.lb(state.dm, defSitePC) + case Assignment(_, _, _: NonVirtualFunctionCall[V]) => FinalIPResult.lb(state.dm, defSitePC) - state.tac.stmts(defSite) match { - case Assignment(_, _, expr: StringConst) => - StringConstInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: IntConst) => - IntegerValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: FloatConst) => - FloatValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: DoubleConst) => - DoubleValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: BinaryExpr[V]) => - BinaryExprInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: ArrayLoad[V]) => - L0ArrayAccessInterpreter(this).interpret(expr, defSite)(state) - case Assignment(_, _, _: New) => - state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) - NoIPResult - case Assignment(_, _, _: GetField[V]) => - // Currently unsupported - FinalIPResult.lb case Assignment(_, _, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) - case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) - case Assignment(_, _, _: NonVirtualFunctionCall[V]) => - // Currently unsupported - FinalIPResult.lb case ExprStmt(_, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) + + case Assignment(_, _, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) - case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) - case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) - case _ => - state.appendToInterimFpe2Sci(defSitePC, StringConstancyInformation.getNeutralElement) - NoIPResult + + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) + case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) + + case _ => NoIPResult(state.dm, defSitePC) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 3dbc361ef3..d4e0078c80 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -8,7 +8,6 @@ package l0 package interpretation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -18,9 +17,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { - - implicit val _exprHandler: InterpretationHandler[State] = exprHandler +) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { override type T = NonVirtualMethodCall[V] @@ -39,7 +36,7 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]] override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { instr.name match { case "" => interpretInit(instr) - case _ => NoIPResult + case _ => NoIPResult(state.dm, instr.pc) } } @@ -51,24 +48,26 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]] */ private def interpretInit(init: T)(implicit state: State): IPResult = { init.params.size match { - case 0 => NoIPResult + case 0 => NoIPResult(state.dm, init.pc) case _ => - val resultsWithPC = init.params.head.asVar.definedBy.toList.map { ds: Int => - (pcOfDefSite(ds)(state.tac.stmts), handleDependentDefSite(ds)) - } - if (resultsWithPC.forall(_._2.isFinal)) { - FinalIPResult(StringConstancyInformation.reduceMultiple(resultsWithPC.map(_._2.asFinal.sci))) + val results = init.params.head.asVar.definedBy.toList.map(exprHandler.processDefSite) + if (results.forall(_.isFinal)) { + finalResult(init.pc)(results) } else { - // Some intermediate results => register necessary information from final results and return an - // intermediate result - // IMPROVE DO PROPER DEPENDENCY HANDLING - resultsWithPC.foreach { resultWithPC => - if (resultWithPC._2.isFinal) { - state.appendToFpe2Sci(resultWithPC._1, resultWithPC._2.asFinal.sci, reset = true) - } - } - InterimIPResult.lb + InterimIPResult.lbWithIPResultDependees( + state.dm, + init.pc, + results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(init, init.pc, state, results), + finalResult(init.pc) + ) + ) } } } + + private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { + FinalIPResult(StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)), state.dm, pc) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 5a227ca637..123f4cd5e5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -9,12 +9,16 @@ package interpretation import scala.util.Try +import org.opalj.br.Method import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.TACAI /** * Processes [[StaticFunctionCall]]s in without a call graph. @@ -27,7 +31,9 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( implicit p: SomeProject, ps: PropertyStore -) extends L0StringInterpreter[State] { +) extends L0StringInterpreter[State] + with IPResultDependingStringInterpreter[State] + with EPSDependingStringInterpreter[State] { override type T = StaticFunctionCall[V] @@ -39,16 +45,8 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } } - private def processStringValueOf(call: StaticFunctionCall[V])( - implicit state: State - ): IPResult = { - val results = call.params.head.asVar.definedBy.toArray.sorted.map { - exprHandler.processDefSite(_) - } - val interim = results.find(_.isRefinable) - if (interim.isDefined) { - InterimIPResult.lb - } else { + private def processStringValueOf(call: T)(implicit state: State): IPResult = { + def finalResult(results: Iterable[IPResult]): FinalIPResult = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.asFinal.sci } val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { @@ -62,7 +60,23 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( } else { scis } - FinalIPResult(StringConstancyInformation.reduceMultiple(finalScis)) + FinalIPResult(StringConstancyInformation.reduceMultiple(finalScis), state.dm, call.pc) + } + + val results = call.params.head.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite) + val hasRefinableResults = results.exists(_.isRefinable) + if (hasRefinableResults) { + InterimIPResult.lbWithIPResultDependees( + state.dm, + call.pc, + results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(call, call.pc, state, results.toIndexedSeq), + finalResult _ + ) + ) + } else { + finalResult(results) } } @@ -71,71 +85,106 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( ): IPResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalIPResult(StringConstancyInformation.lb) + return FinalIPResult.lb(state.dm, instr.pc) } - // 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 params = if (state.nonFinalFunctionArgs.contains(instr)) { - state.nonFinalFunctionArgs(instr) + val m = calleeMethod.value + val (tacEOptP, calleeTac) = getTACAI(ps, m, state) + + if (tacEOptP.isRefinable) { + InterimIPResult.lbWithEPSDependees( + state.dm, + instr.pc, + Seq(tacEOptP), + awaitAllFinalContinuation( + SimpleEPSDepender(instr, instr.pc, state, Seq(tacEOptP)), + (finalEPs: Iterable[SomeFinalEP]) => + interpretArbitraryCallWithCalleeTAC(instr, defSite, m)( + finalEPs.head.ub.asInstanceOf[TACAI].tac.get + ) + ) + ) + } else if (calleeTac.isEmpty) { + // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all + FinalIPResult.lb(state.dm, instr.pc) } else { - evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) + interpretArbitraryCallWithCalleeTAC(instr, defSite, m)(calleeTac.get) } + } - val m = calleeMethod.value - val (tacEOptP, calleeTac) = getTACAI(ps, m, state) + private def interpretArbitraryCallWithCalleeTAC(instr: T, defSite: Int, calleeMethod: Method)( + calleeTac: TAC + )(implicit state: State): IPResult = { + val returns = calleeTac.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 + return FinalIPResult.lb(state.dm, instr.pc) + } - // Continue only when all parameter information are available + val params = evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (refinableResults.nonEmpty) { - // question why do we depend on the return value of the call - if (calleeTac.isDefined) { - val returns = calleeTac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) - returns.foreach { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m) - val eps = ps(entity, StringConstancyProperty.key) - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) - } - } else { - state.dependees = tacEOptP :: state.dependees - } state.nonFinalFunctionArgs(instr) = params - return InterimIPResult.lb - } + InterimIPResult.lbWithIPResultDependees( + state.dm, + instr.pc, + refinableResults.asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, instr.pc, state, refinableResults), + (_: Iterable[IPResult]) => { + val params = state.nonFinalFunctionArgs(instr) + state.nonFinalFunctionArgs.remove(instr) - state.nonFinalFunctionArgs.remove(instr) - state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) - if (calleeTac.isDefined) { - // TAC available => Get return UVar and start the string analysis - val returns = calleeTac.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 - FinalIPResult.lb - } else { - val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m) - StringAnalysis.registerParams(entity, evaluatedParams) - - val eps = ps(entity, StringConstancyProperty.key) - if (eps.isRefinable) { - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, calleeMethod)( + calleeTac, + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) + ) } - eps - } - if (results.exists(_.isRefinable)) { - InterimIPResult.lb - } else { - FinalIPResult(results.head.asFinal.p.stringConstancyInformation) + ) + ) + } else { + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, calleeMethod)( + calleeTac, + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) + ) + } + } + + private def interpretArbitraryCallWithCalleeTACAndParams(instr: T, defSite: Int, calleeMethod: Method)( + calleeTac: TAC, + params: Seq[Seq[Seq[FinalIPResult]]] + )(implicit state: State): IPResult = { + def finalResult(results: Iterable[SomeFinalEP]): FinalIPResult = { + val sci = StringConstancyInformation.reduceMultiple( + results.asInstanceOf[Iterable[FinalEP[_, StringConstancyProperty]]].map { + _.p.stringConstancyInformation } - } + ) + FinalIPResult(sci, state.dm, instr.pc) + } + + val evaluatedParams = convertEvaluatedParameters(params) + val returns = calleeTac.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val results = returns.map { ret => + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.stmts), calleeMethod) + state.appendToVar2IndexMapping(entity._1, defSite) + + StringAnalysis.registerParams(entity, evaluatedParams) + ps(entity, StringConstancyProperty.key) + } + if (results.exists(_.isRefinable)) { + InterimIPResult.lbWithEPSDependees( + state.dm, + instr.pc, + results.filter(_.isRefinable), + awaitAllFinalContinuation( + SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), + finalResult _ + ) + ) } else { - state.dependees = tacEOptP :: state.dependees - EmptyIPResult + finalResult(results.toIndexedSeq.asInstanceOf[Iterable[SomeFinalEP]]) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index e1a0ad2a88..e084115f86 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -7,8 +7,6 @@ package string_analysis package l0 package interpretation -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter - /** * @author Maximilian Rüsch */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index def0f292bc..5edb64bddd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -18,46 +18,31 @@ import org.opalj.br.ObjectType 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.interpretation.DependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.value.TheIntegerValue /** * Responsible for processing [[VirtualFunctionCall]]s without a call graph. - * The list of currently supported function calls can be seen in the documentation of [[interpret]]. * * @author Maximilian Rüsch */ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with DependingStringInterpreter[State] { - - implicit val _exprHandler: InterpretationHandler[State] = exprHandler +) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { override type T = VirtualFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { - val result = handleInterpretation(instr, defSite) - - if (result.sciOpt.isDefined) { - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sciOpt.get) - } - result - } - /** * 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. + * `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 - * [[L0VirtualFunctionCallInterpreter.interpretReplaceCall]]. + * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For further information how + * this operation is processed, see [[interpretReplaceCall]]. *
    • *
    • * Apart from these supported methods, a [[StringConstancyInformation.lb]] will be returned in case the passed @@ -67,59 +52,91 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - protected def handleInterpretation(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { instr.name match { case "append" => interpretAppendCall(instr) case "toString" => interpretToStringCall(instr) - case "replace" => interpretReplaceCall + case "replace" => interpretReplaceCall(instr) case "substring" if instr.descriptor.returnType == ObjectType.String => interpretSubstringCall(instr) case _ => instr.descriptor.returnType match { - case obj: ObjectType if obj == ObjectType.String => FinalIPResult.lb - case FloatType | DoubleType => FinalIPResult(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.FloatValue - )) - case _ => NoIPResult + case obj: ObjectType if obj == ObjectType.String => + interpretArbitraryCall(instr, defSite) + case FloatType | DoubleType => FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.FloatValue + ), + state.dm, + instr.pc + ) + case _ => NoIPResult(state.dm, instr.pc) } } } + protected def interpretArbitraryCall(call: T, defSite: Int)(implicit state: State): IPResult = + FinalIPResult.lb(state.dm, call.pc) + /** * 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: T)(implicit state: State): IPResult = { - val receiverSci = receiverValuesOfCall(appendCall) - val appendSci = valueOfAppendCall(appendCall) - - if (appendSci.isEmpty) { - InterimIPResult.lb - } else { - val sci = if (receiverSci.isTheNeutralElement && appendSci.get.isTheNeutralElement) { - // although counter-intuitive, this case may occur if both the receiver and the parameter have been - // processed before + def computeFinalAppendCallResult(receiverResults: Iterable[IPResult], appendResult: IPResult): FinalIPResult = { + val receiverScis = receiverResults.map(_.asFinal.sci) + val appendSci = appendResult.asFinal.sci + val areAllReceiversNeutral = receiverScis.forall(_.isTheNeutralElement) + val sci = if (areAllReceiversNeutral && appendSci.isTheNeutralElement) { + // although counter-intuitive, this case occurs if both receiver and parameter have been processed before StringConstancyInformation.getNeutralElement - } else if (receiverSci.isTheNeutralElement) { + } else if (areAllReceiversNeutral) { // 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 - appendSci.get - } else if (appendSci.get.isTheNeutralElement) { + appendSci + } else if (appendSci.isTheNeutralElement) { // The append value might be empty, if the site has already been processed (then this // information will come from another StringConstancyInformation object - receiverSci + StringConstancyInformation.reduceMultiple(receiverScis) } else { // Receiver and parameter information are available => combine them + val receiverSci = StringConstancyInformation.reduceMultiple(receiverScis) StringConstancyInformation( - StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.get.constancyLevel), + StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.constancyLevel), StringConstancyType.APPEND, - receiverSci.possibleStrings + appendSci.get.possibleStrings + receiverSci.possibleStrings + appendSci.possibleStrings ) } - FinalIPResult(sci) + FinalIPResult(sci, state.dm, appendCall.pc) + } + + val receiverResults = receiverValuesOfCall(appendCall) + val appendResult = valueOfAppendCall(appendCall) + + if (receiverResults.exists(_.isRefinable) || appendResult.isRefinable) { + val allRefinableResults = if (appendResult.isRefinable) { + receiverResults.filter(_.isRefinable) :+ appendResult + } else receiverResults.filter(_.isRefinable) + + InterimIPResult.lbWithIPResultDependees( + state.dm, + appendCall.pc, + allRefinableResults.asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(appendCall, appendCall.pc, state, receiverResults :+ appendResult), + (results: Iterable[IPResult]) => { + computeFinalAppendCallResult( + results.filter(_.e != appendResult.e), + results.find(_.e == appendResult.e).get + ) + } + ) + ) + } else { + computeFinalAppendCallResult(receiverResults, appendResult) } } @@ -127,105 +144,153 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * 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: T)(implicit state: State): Option[StringConstancyInformation] = { - val param = call.params.head.asVar + private def valueOfAppendCall(call: T)(implicit state: State): IPResult = { // .head because we want to evaluate only the first argument of append - val defSiteHead = param.definedBy.head - var value = handleDependentDefSite(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.isNoResult) { - value = handleDependentDefSite(state.tac.stmts(defSiteHead).asAssignment.targetVar.usedBy.toArray.min) - } + val param = call.params.head.asVar + val defSites = param.definedBy.toArray.sorted + + def computeFinalAppendValueResult(results: Iterable[IPResult]): FinalIPResult = { + val sciValues = results.map(_.asFinal.sci) + val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) - if (value.isRefinable) { - None - } else { - val sci = value.asFinal.sci 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 + newValueSci.constancyLevel == StringConstancyLevel.CONSTANT && + sciValues.exists(!_.isTheNeutralElement) ) { - if (Try(sci.possibleStrings.toInt).isSuccess) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) - } else - sci + val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => + if (Try(sci.possibleStrings.toInt).isSuccess) { + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } else { + sci + } + } + StringConstancyInformation.reduceMultiple(charSciValues) } else { - sci + newValueSci } case ComputationalTypeFloat | ComputationalTypeDouble => - if (sci.constancyLevel == StringConstancyLevel.CONSTANT) { - sci + if (newValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { + newValueSci } else { InterpretationHandler.getConstancyInfoForDynamicFloat } - // Otherwise, try to compute case _ => - sci + newValueSci } - Some(finalSci) + FinalIPResult(finalSci, state.dm, call.pc) } + + if (defSites.exists(_ < 0)) { + return FinalIPResult.lb(state.dm, call.pc) + } + + val valueResults = defSites.map { ds => + state.tac.stmts(ds) match { + // If a site points to a "New", process the first use site + case Assignment(_, targetVar, _: New) => exprHandler.processDefSite(targetVar.usedBy.toArray.min) + case _ => exprHandler.processDefSite(ds) + } + } + + // Defer the computation if there is at least one intermediate result + if (valueResults.exists(_.isRefinable)) { + return InterimIPResult.lbWithIPResultDependees( + state.dm, + call.pc, + valueResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(call, call.pc, state, valueResults.toIndexedSeq), + computeFinalAppendValueResult + ) + ) + } + + computeFinalAppendValueResult(valueResults) } /** * Processes calls to [[String#substring]]. */ private def interpretSubstringCall(substringCall: T)(implicit state: State): IPResult = { - val receiverSci = receiverValuesOfCall(substringCall) - - if (receiverSci.isComplex) { - // We cannot yet interpret substrings of mixed values - FinalIPResult.lb - } else { - val parameterCount = substringCall.params.size - parameterCount match { - case 1 => - substringCall.params.head.asVar.value match { - case intValue: TheIntegerValue => - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.REPLACE, - receiverSci.possibleStrings.substring(intValue.value) - )) - case _ => - FinalIPResult.lb - } + def computeFinalSubstringCallResult(results: Iterable[IPResult]): FinalIPResult = { + val receiverSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) + if (receiverSci.isComplex) { + // We cannot yet interpret substrings of mixed values + FinalIPResult.lb(state.dm, substringCall.pc) + } else { + val parameterCount = substringCall.params.size + parameterCount match { + case 1 => + substringCall.params.head.asVar.value match { + case intValue: TheIntegerValue => + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.REPLACE, + receiverSci.possibleStrings.substring(intValue.value) + ), + state.dm, + substringCall.pc + ) + case _ => + FinalIPResult.lb(state.dm, substringCall.pc) + } - case 2 => - (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { - case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => - FinalIPResult(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) - )) - case _ => - FinalIPResult.lb - } + case 2 => + (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { + case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => + FinalIPResult( + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) + ), + state.dm, + substringCall.pc + ) + case _ => + FinalIPResult.lb(state.dm, substringCall.pc) + } - case _ => throw new IllegalStateException( - s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" - ) + case _ => throw new IllegalStateException( + s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" + ) + } } } + + val receiverResults = receiverValuesOfCall(substringCall) + if (receiverResults.forall(_.isNoResult)) { + return FinalIPResult.lb(state.dm, substringCall.pc) + } + + if (receiverResults.exists(_.isRefinable)) { + InterimIPResult.lbWithIPResultDependees( + state.dm, + substringCall.pc, + receiverResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(substringCall, substringCall.pc, state, receiverResults), + computeFinalSubstringCallResult + ) + ) + } else { + computeFinalSubstringCallResult(receiverResults) + } } /** * This function determines the current value of the receiver object of a call. */ - private def receiverValuesOfCall(call: T)(implicit state: State): 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 - val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds => - // IMPROVE enable handling dependees here - exprHandler.processDefSite(ds).asFinal.sci - }.filter { sci => !sci.isTheNeutralElement } - scis.headOption.getOrElse(StringConstancyInformation.getNeutralElement) + private def receiverValuesOfCall(call: T)(implicit state: State): Seq[IPResult] = { + val defSites = call.receiver.asVar.definedBy.toArray.sorted + val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) + allResults.foreach { r => state.fpe2ipr(r._1) = r._2 } + + allResults.toIndexedSeq.map(_._2) } /** @@ -233,12 +298,12 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * the given `toString` is such a function call! Otherwise, the expected behavior cannot be guaranteed. */ private def interpretToStringCall(call: T)(implicit state: State): IPResult = { - handleDependentDefSite(call.receiver.asVar.definedBy.head) + FallThroughIPResult(state.dm, call.pc, call.receiver.asVar.definedBy.head) } /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall: IPResult = - FinalIPResult(InterpretationHandler.getStringConstancyInformationForReplace) + private def interpretReplaceCall(call: T)(implicit state: State): IPResult = + FinalIPResult(InterpretationHandler.getStringConstancyInformationForReplace, state.dm, call.pc) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 83535a8ded..a71c314272 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -12,12 +12,12 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** - * Responsible for processing [[VirtualMethodCall]]s in an intraprocedural fashion. - * For supported method calls, see the documentation of the `interpret` function. + * Responsible for processing [[VirtualMethodCall]]s without a call graph. * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() extends L0StringInterpreter[State] { +case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() + extends L0StringInterpreter[State] with SingleStepStringInterpreter[State] { override type T = VirtualMethodCall[V] @@ -34,12 +34,15 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() * * For all other calls, a [[NoIPResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult = { instr.name match { + // IMPROVE interpret argument for setLength case "setLength" => FinalIPResult( - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET), + state.dm, + instr.pc ) - case _ => NoIPResult + case _ => NoIPResult(state.dm, instr.pc) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index 6d64522a4a..c95f5d0c41 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -6,15 +6,19 @@ package analyses package string_analysis package l1 +import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState trait L1ComputationState[State <: L1ComputationState[State]] extends L0ComputationState[State] { val methodContext: Context + var calleesDependee: Option[EOptionP[DefinedMethod, Callees]] = _ + /** * Callees information regarding the declared method that corresponds to the entity's method */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 44807e21f4..6861dca7cf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -8,7 +8,7 @@ package l1 import scala.collection.mutable.ListBuffer -import org.opalj.br.DeclaredMethod +import org.opalj.br.DefinedMethod import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.analyses.DeclaredFields @@ -41,9 +41,6 @@ import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -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.properties.TACAI @@ -75,7 +72,7 @@ import org.opalj.tac.fpcf.properties.TACAI class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { protected[l1] case class CState( - override val dm: DeclaredMethod, + override val dm: DefinedMethod, override val entity: (SEntity, Method), override val methodContext: Context ) extends L1ComputationState[CState] @@ -117,27 +114,29 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) + state.iHandler = L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) val tacaiEOptP = ps(data._2, TACAI.key) - if (tacaiEOptP.hasUBP) { - 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 + if (tacaiEOptP.isRefinable) { + state.tacDependee = Some(tacaiEOptP) + return getInterimResult(state) } + if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + return Result(state.entity, StringConstancyProperty.lb) + } + + state.tac = tacaiEOptP.ub.tac.get + val calleesEOptP = ps(dm, Callees.key) - if (calleesEOptP.hasUBP) { - state.callees = calleesEOptP.ub - determinePossibleStrings(state) - } else { - state.dependees = calleesEOptP :: state.dependees - getInterimResult(state) + if (calleesEOptP.hasNoUBP) { + state.calleesDependee = Some(calleesEOptP) + return getInterimResult(state) } + + state.callees = calleesEOptP.ub + determinePossibleStrings(state) } /** @@ -162,13 +161,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state.computedLeanPath = computeLeanPath(uVar)(state.tac) } - if (state.iHandler == null) { - state.iHandler = - L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) - state.interimIHandler = - L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) - } - var requiresCallersInfo = false if (state.params.isEmpty) { state.params = StringAnalysis.getParams(state.entity) @@ -225,8 +217,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.parameterDependeesCount > 0) { return getInterimResult(state) - } else { - state.isSetupCompleted = true } // Interpret a function / method parameter using the parameter information in state @@ -246,7 +236,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) ep match { case FinalEP(e, p) => - processFinalP(state, e, p) + state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run if (state.dependees.isEmpty) { return computeFinalResult(state) @@ -266,8 +256,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { && state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath)(state) ) { - new PathTransformer(state.iHandler) - .pathToStringTree(state.computedLeanPath, state.fpe2sci) + PathTransformer + .pathToStringTree(state.computedLeanPath)(state) .reduce(true) } else { StringConstancyInformation.lb @@ -314,20 +304,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } } - override protected[string_analysis] def finalizePreparations( - path: Path, - state: State, - iHandler: InterpretationHandler[State] - ): Unit = path.elements.foreach { - case fpe: FlatPathElement => - if (!state.fpe2sci.contains(fpe.pc)) { - iHandler.finalizeDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get)(state) - } - case npe: NestedPathElement => - finalizePreparations(Path(npe.element.toList), state, iHandler) - case _ => - } - /** * This method takes a computation `state`, and determines the interpretations of all parameters of the method under * analysis. These interpretations are registered using [[StringAnalysis.registerParams]]. The return value of this @@ -393,7 +369,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } override protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { - case al: ArrayLoad[V] => L0ArrayAccessInterpreter.getStoreAndLoadDefSites(al)(tac.stmts).exists(_ < 0) + case al: ArrayLoad[V] => L0ArrayAccessInterpreter.getStoreAndLoadDefSitePCs(al)(tac.stmts).exists(_ < 0) case _ => super.hasExprFormalParamUsage(expr) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala deleted file mode 100644 index 14e39fd4ba..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/ArrayLoadFinalizer.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -import scala.collection.mutable.ListBuffer - -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter - -/** - * @author Maximilian Rüsch - */ -case class ArrayLoadFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override type T = ArrayLoad[V] - - /** - * Finalizes [[ArrayLoad]]s. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { - val allDefSites = L0ArrayAccessInterpreter.getStoreAndLoadDefSites(instr)(state.tac.stmts) - val allDefSitesByPC = allDefSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - allDefSitesByPC.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(allDefSitesByPC(pc)) - } - } - - state.fpe2sci(pcOfDefSite(defSite)(state.tac.stmts)) = ListBuffer(StringConstancyInformation.reduceMultiple( - allDefSitesByPC.keys.filter(state.fpe2sci.contains).toList.sorted.flatMap { pc => state.fpe2sci(pc) } - )) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala deleted file mode 100644 index 118c61cdc8..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/FieldReadFinalizer.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -/** - * @author Maximilian Rüsch - */ -case class FieldReadFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override protected type T = FieldRead[V] - - /** - * Finalizes [[FieldRead]]s. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = - // Processing the definition site again is enough as the finalization procedure is only - // called after all dependencies are resolved. - state.iHandler.processDefSite(defSite)(state) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala deleted file mode 100644 index 2e7e31a087..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/L1Finalizer.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -/** - * 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 trait 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!''' - */ -trait L1Finalizer[State <: L1ComputationState[State]] { - - 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 [[ComputationState.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)(implicit state: State): Unit -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala deleted file mode 100644 index 156928c88a..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NewArrayFinalizer.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -/** - * @author Maximilian Rüsch - */ -case class NewArrayFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override type T = NewArray[V] - - /** - * Finalizes [[NewArray]]s. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = - // Simply re-trigger the computation - state.iHandler.processDefSite(defSite)(state) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala deleted file mode 100644 index 7bfa371711..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/NonVirtualMethodCallFinalizer.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation - -/** - * @author Maximilian Rüsch - */ -case class NonVirtualMethodCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override type T = NonVirtualMethodCall[V] - - /** - * Finalizes [[NonVirtualMethodCall]]s. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { - val toAppend = if (instr.params.nonEmpty) { - val defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - defSitesByPC.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(defSitesByPC(pc)) - } - } - StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) - } else { - StringConstancyInformation.lb - } - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), toAppend, reset = true) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala deleted file mode 100644 index dedd5c4c39..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/StaticFunctionCallFinalizer.scala +++ /dev/null @@ -1,43 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation - -/** - * @author Maximilian Rüsch - */ -case class StaticFunctionCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override type T = StaticFunctionCall[V] - - /** - * Finalizes [[StaticFunctionCall]]s. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): 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 defSitesByPC = instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - defSitesByPC.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(defSitesByPC(pc)) - } - } - StringConstancyInformation.reduceMultiple(defSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci)) - } else { - StringConstancyInformation.lb - } - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), toAppend, reset = true) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala deleted file mode 100644 index b21740f241..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/finalizer/VirtualFunctionCallFinalizer.scala +++ /dev/null @@ -1,105 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package finalizer - -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 - -/** - * @author Maximilian Rüsch - */ -case class VirtualFunctionCallFinalizer[State <: L1ComputationState[State]]() extends L1Finalizer[State] { - - override type T = VirtualFunctionCall[V] - - /** - * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" and - * "toString" function. - *

      - * @inheritdoc - */ - override def finalizeInterpretation(instr: T, defSite: Int)(implicit state: State): Unit = { - instr.name match { - case "append" => finalizeAppend(instr, defSite) - case "toString" => finalizeToString(instr, defSite) - case _ => - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb, reset = true) - } - } - - /** - * This function actually finalizes append calls by mimicking the behavior of the corresponding - * interpretation function of - * [[org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1VirtualFunctionCallInterpreter]]. - */ - private def finalizeAppend(instr: T, defSite: Int)(implicit state: State): Unit = { - val receiverDefSitesByPC = - instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - receiverDefSitesByPC.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(receiverDefSitesByPC(pc)) - } - } - val receiverSci = StringConstancyInformation.reduceMultiple( - receiverDefSitesByPC.keys.toList.sorted.flatMap { pc => - // 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(pc) - state.appendToFpe2Sci(pc, StringConstancyInformation.getNeutralElement, reset = true) - sci - } - ) - - val paramDefSitesByPC = - instr.params.head.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - paramDefSitesByPC.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(paramDefSitesByPC(pc)) - } - } - val appendSci = if (paramDefSitesByPC.keys.forall(state.fpe2sci.contains)) { - StringConstancyInformation.reduceMultiple(paramDefSitesByPC.keys.toList.sorted.flatMap(state.fpe2sci(_))) - } else StringConstancyInformation.lb - - 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.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci, reset = true) - } - - private def finalizeToString(instr: T, defSite: Int)(implicit state: State): Unit = { - val dependeeSites = instr.receiver.asVar.definedBy.map(ds => (pcOfDefSite(ds)(state.tac.stmts), ds)).toMap - dependeeSites.keys.foreach { pc => - if (!state.fpe2sci.contains(pc)) { - state.iHandler.finalizeDefSite(dependeeSites(pc)) - } - } - val finalSci = StringConstancyInformation.reduceMultiple( - dependeeSites.keys.toList.flatMap { pc => state.fpe2sci(pc) } - ) - // 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.keys.foreach { - state.appendToFpe2Sci(_, StringConstancyInformation.getNeutralElement, reset = true) - } - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 4d1416de98..1a3de2b11e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -71,16 +71,18 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( * [[StringConstancyLevel.DYNAMIC]]. */ override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { - return FinalIPResult.lb + return FinalIPResult.lb(state.dm, defSitePC) } val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { - return FinalIPResult.lb + return FinalIPResult.lb(state.dm, defSitePC) } var hasInit = false @@ -95,9 +97,9 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( val (tacEps, tac) = getTACAI(ps, method, state) val nextResult = if (parameter.isEmpty) { // Field parameter information is not available - FinalIPResult.lb + FinalIPResult.lb(state.dm, defSitePC) } else if (tacEps.isRefinable) { - EmptyIPResult + EmptyIPResult(state.dm, defSitePC) } else { tac match { case Some(_) => @@ -111,13 +113,13 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( // though it might not be. Thus, we use -1 as it is a safe dummy // value state.appendToVar2IndexMapping(entity._1, -1) - InterimIPResult(eps.lb.stringConstancyInformation) + EmptyIPResult(state.dm, defSitePC) } else { - FinalIPResult(eps.asFinal.p.stringConstancyInformation) + FinalIPResult(eps.asFinal.p.stringConstancyInformation, state.dm, defSitePC) } case _ => // No TAC available - FinalIPResult.lb + FinalIPResult.lb(state.dm, defSitePC) } } results.append(nextResult) @@ -130,21 +132,20 @@ case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( possibleStrings = s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" ) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - FinalIPResult(sci) + FinalIPResult(sci, state.dm, defSitePC) } else { 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(FinalIPResult.nullElement) + results.append(FinalIPResult.nullElement(state.dm, defSitePC)) } val finalSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), finalSci) - FinalIPResult(finalSci) + FinalIPResult(finalSci, state.dm, defSitePC) } else { - InterimIPResult.lb + // IMPROVE return interim result here and depend on EPS + EmptyIPResult(state.dm, defSitePC) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 924b266a4a..6c70262350 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -7,12 +7,10 @@ package string_analysis package l1 package interpretation -import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter @@ -24,21 +22,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAcce import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMethodCallInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.ArrayLoadFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.FieldReadFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NewArrayFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.NonVirtualMethodCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.StaticFunctionCallFinalizer -import org.opalj.tac.fpcf.analyses.string_analysis.l1.finalizer.VirtualFunctionCallFinalizer /** - * 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. - *

      - * This handler may use [[L1StringInterpreter]]s and general - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter]]s. + * @inheritdoc * - * @author Patrick Mell + * @author Maximilian Rüsch */ class L1InterpretationHandler[State <: L1ComputationState[State]]( declaredFields: DeclaredFields, @@ -48,276 +36,67 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( implicit val contextProvider: ContextProvider ) extends InterpretationHandler[State] { - /** - * Processed the given definition site in an interprocedural fashion. - *

      - * - * @inheritdoc - */ - override def processDefSite(defSite: Int)(implicit state: State): IPResult = { - // Function parameters are not evaluated when none are present (this always includes the - // implicit parameter for "this" and for exceptions thrown outside the current function) - if (defSite < 0) { - val params = state.params.toList.map(_.toList) - if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - return FinalIPResult.lb - } else { - val sci = getParam(params, defSite) - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - return FinalIPResult(sci) - } - } else if (processedDefSites.contains(defSite)) { - state.appendToInterimFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - StringConstancyInformation.getNeutralElement - ) - return NoIPResult - } - // Note that def sites referring to constant expressions will be deleted further down - processedDefSites(defSite) = () + override protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult = { + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) state.tac.stmts(defSite) match { - 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) // TODO what about long consts - case Assignment(_, _, expr: ArrayLoad[V]) => processArrayLoad(expr, defSite) - case Assignment(_, _, expr: NewArray[V]) => processNewArray(expr, defSite) - case Assignment(_, _, _: New) => - state.appendToInterimFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - StringConstancyInformation.getNeutralElement - ) - NoIPResult - case Assignment(_, _, expr: GetStatic) => processGetField(expr, defSite) - case ExprStmt(_, expr: GetStatic) => processGetField(expr, defSite) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => processVFC(expr, defSite) - case Assignment(_, _, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) => processStaticFunctionCall(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) => processBinaryExpr(expr, defSite) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => processNonVirtualFunctionCall(expr, defSite) - case Assignment(_, _, expr: GetField[V]) => processGetField(expr, defSite) - case vmc: VirtualMethodCall[V] => processVirtualMethodCall(vmc, defSite) - case nvmc: NonVirtualMethodCall[V] => processNonVirtualMethodCall(nvmc, defSite) - case _ => - state.appendToInterimFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - StringConstancyInformation.getNeutralElement - ) - NoIPResult - } - } - - /** - * Helper / utility function for processing [[StringConst]], [[IntConst]], [[FloatConst]], and - * [[DoubleConst]]. - */ - private def processConstExpr( - constExpr: SimpleValueConst, - defSite: Int - )(implicit state: State): FinalIPResult = { - val result = constExpr match { - case ic: IntConst => IntegerValueInterpreter.interpret(ic, defSite)(state) - case fc: FloatConst => FloatValueInterpreter.interpret(fc, defSite)(state) - case dc: DoubleConst => DoubleValueInterpreter.interpret(dc, defSite)(state) - case sc: StringConst => StringConstInterpreter.interpret(sc, defSite)(state) - case c => throw new IllegalArgumentException(s"Unsupported const value: $c") - } - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sci) - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), result.sci) - processedDefSites.remove(defSite) - result - } - - /** - * Helper / utility function for processing [[ArrayLoad]]s. - */ - private def processArrayLoad( - expr: ArrayLoad[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = new L0ArrayAccessInterpreter(this).interpret(expr, defSite) - val sci = if (r.isFinal) { - r.asFinal.sci - } else { - processedDefSites.remove(defSite) - StringConstancyInformation.lb - } - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - r - } - - /** - * Helper / utility function for processing [[NewArray]]s. - */ - private def processNewArray( - expr: NewArray[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = new L1NewArrayInterpreter(this).interpret(expr, defSite) - val sci = if (r.isFinal) { - r.asFinal.sci - } else { - processedDefSites.remove(defSite) - StringConstancyInformation.lb - } - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - r - } - - /** - * Helper / utility function for interpreting [[VirtualFunctionCall]]s. - */ - private def processVFC( - expr: VirtualFunctionCall[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) - doInterimResultHandling(r, defSite) - r - } - - /** - * Helper / utility function for processing [[StaticFunctionCall]]s. - */ - private def processStaticFunctionCall( - expr: StaticFunctionCall[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) - if (r.isRefinable || 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)(implicit state: State): IPResult = { - // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - val result = BinaryExprInterpreter.interpret(expr, defSite)(state) - doInterimResultHandling(result, defSite) - result - } - - /** - * Helper / utility function for processing [[GetField]]s. - */ - private def processGetField(expr: FieldRead[V], defSite: Int)(implicit state: State): IPResult = { - val r = L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider) - .interpret(expr, defSite)(state) - if (r.isRefinable) { - processedDefSites.remove(defSite) - } - doInterimResultHandling(r, defSite) - r - } - - /** - * Helper / utility function for processing [[NonVirtualMethodCall]]s. - */ - private def processNonVirtualFunctionCall( - expr: NonVirtualFunctionCall[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) - if (r.isRefinable || 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 - )(implicit state: State): IPResult = { - val r = L0VirtualMethodCallInterpreter().interpret(expr, defSite)(state) - doInterimResultHandling(r, defSite) - r - } - - /** - * Helper / utility function for processing [[NonVirtualMethodCall]]s. - */ - private def processNonVirtualMethodCall( - nvmc: NonVirtualMethodCall[V], - defSite: Int - )(implicit state: State): IPResult = { - val r = L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) - r match { - case FinalIPResult(sci) => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - case InterimIPResult(interimSci) => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), interimSci) - processedDefSites.remove(defSite) - case _ => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - processedDefSites.remove(defSite) - } - r - } - - /** - * Takes a result, which can be final or not, as well as a definition site. Takes the steps necessary to provide - * information for computing intermediate results. - */ - private def doInterimResultHandling(result: IPResult, defSite: Int)(implicit state: State): Unit = { - result match { - case FinalIPResult(sci) => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), sci) - case InterimIPResult(interimSci) => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), interimSci) - case _ => - state.appendToInterimFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), StringConstancyInformation.lb) - } - } - - /** - * Finalized a given definition state. - */ - override def finalizeDefSite(defSite: Int)(implicit state: State): Unit = { - if (defSite < 0) { - state.appendToFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - getParam(state.params.toSeq.map(_.toSeq), defSite), - reset = true - ) - } else { - state.tac.stmts(defSite) match { - case nvmc: NonVirtualMethodCall[V] => - NonVirtualMethodCallFinalizer().finalizeInterpretation(nvmc, defSite)(state) - case Assignment(_, _, al: ArrayLoad[V]) => - ArrayLoadFinalizer().finalizeInterpretation(al, defSite)(state) - case Assignment(_, _, na: NewArray[V]) => - NewArrayFinalizer().finalizeInterpretation(na, defSite)(state) - case Assignment(_, _, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer().finalizeInterpretation(vfc, defSite)(state) - case ExprStmt(_, vfc: VirtualFunctionCall[V]) => - VirtualFunctionCallFinalizer().finalizeInterpretation(vfc, defSite)(state) - case Assignment(_, _, fr: FieldRead[V]) => - FieldReadFinalizer().finalizeInterpretation(fr, defSite)(state) - case ExprStmt(_, fr: FieldRead[V]) => - FieldReadFinalizer().finalizeInterpretation(fr, defSite)(state) - case Assignment(_, _, sfc: StaticFunctionCall[V]) => - StaticFunctionCallFinalizer().finalizeInterpretation(sfc, defSite)(state) - case ExprStmt(_, sfc: StaticFunctionCall[V]) => - StaticFunctionCallFinalizer().finalizeInterpretation(sfc, defSite)(state) - case _ => - state.appendToFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - StringConstancyInformation.lb, - reset = true - ) - } + case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite)(state) // TODO what about long consts + + case Assignment(_, _, expr: ArrayLoad[V]) => + new L0ArrayAccessInterpreter(this).interpret(expr, defSite) + case Assignment(_, _, expr: NewArray[V]) => + new L1NewArrayInterpreter(this).interpret(expr, defSite) + + case Assignment(_, _, _: New) => NoIPResult(state.dm, defSitePC) + + case Assignment(_, _, expr: GetStatic) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( + expr, + defSite + )(state) + case Assignment(_, _, expr: GetField[V]) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( + expr, + defSite + )(state) + case ExprStmt(_, expr: GetStatic) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( + expr, + defSite + )(state) + case ExprStmt(_, expr: GetField[V]) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( + expr, + defSite + )(state) + + case Assignment(_, _, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) + case ExprStmt(_, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) + + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) + case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) + + case Assignment(_, _, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + case ExprStmt(_, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + + // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite)(state) + + case vmc: VirtualMethodCall[V] => + L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) + case nvmc: NonVirtualMethodCall[V] => + L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) + + case _ => NoIPResult(state.dm, defSitePC) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 4562617a01..333f851582 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -8,34 +8,26 @@ package l1 package interpretation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.fpcf.analyses.string_analysis.IPResultDependingStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for preparing [[NewArray]] expressions. + * Interprets [[NewArray]] expressions without a call graph. *

      - * 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. * - * @author Patrick Mell + * @author Maximilian Rüsch */ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( exprHandler: InterpretationHandler[State] -) extends L1StringInterpreter[State] { +) extends L1StringInterpreter[State] with IPResultDependingStringInterpreter[State] { override type T = NewArray[V] - /** - * @note This implementation will extend [[ComputationState.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 refinable result will have to be handled later on to - * not miss this information. - */ override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { - // Only support for 1-D arrays + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) if (instr.counts.length != 1) { - return FinalIPResult.lb + // Only supports 1-D arrays + return FinalIPResult.lb(state.dm, defSitePC) } // Get all sites that define array values and process them @@ -46,11 +38,7 @@ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( }.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) - if (r.isFinal) { - state.appendToFpe2Sci(pcOfDefSite(d)(state.tac.stmts), r.asFinal.sci) - } - r + exprHandler.processDefSite(d) } } @@ -59,26 +47,35 @@ class L1NewArrayInterpreter[State <: L1ComputationState[State]]( val paramPos = Math.abs(ds + 2) // IMPROVE should we use lb as the fallback value val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) - state.appendToFpe2Sci(pcOfDefSite(ds)(state.tac.stmts), sci) - allResults ::= FinalIPResult(sci) + val r = FinalIPResult(sci, state.dm, pcOfDefSite(ds)(state.tac.stmts)) + state.fpe2ipr(pcOfDefSite(ds)(state.tac.stmts)) = r + allResults ::= r } - val interims = allResults.find(!_.isFinal) - if (interims.isDefined) { - InterimIPResult.lb + if (allResults.exists(_.isRefinable)) { + InterimIPResult.lbWithIPResultDependees( + state.dm, + defSitePC, + allResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, defSitePC, state, allResults), + finalResult(defSitePC) + ) + ) } else { - var resultSci = StringConstancyInformation.reduceMultiple(allResults.map(_.asFinal.sci)) + finalResult(defSitePC)(allResults) + } + } + + private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { + val resultSci = if (results.forall(_.isNoResult)) { + StringConstancyInformation.lb // 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 = FinalIPResult(resultSci) - allResults = toAppend :: allResults - } - state.appendToFpe2Sci(pcOfDefSite(defSite)(state.tac.stmts), resultSci) - FinalIPResult(resultSci) + } else { + StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) } + + FinalIPResult(resultSci, state.dm, pc) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 990ef20e47..989d3511cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -9,7 +9,12 @@ package interpretation import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeFinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.EPSDependingStringInterpreter /** * Responsible for processing [[NonVirtualFunctionCall]]s with a call graph. @@ -19,7 +24,7 @@ import org.opalj.fpcf.PropertyStore case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State]]()( implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider -) extends L1StringInterpreter[State] { +) extends L1StringInterpreter[State] with EPSDependingStringInterpreter[State] { override type T = NonVirtualFunctionCall[V] @@ -27,7 +32,7 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State val methods = getMethodsForPC(instr.pc) if (methods._1.isEmpty) { // No methods available => Return lower bound - return FinalIPResult.lb + return FinalIPResult.lb(state.dm, instr.pc) } val m = methods._1.head @@ -38,13 +43,13 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, is approximated // with the lower bound - FinalIPResult.lb + FinalIPResult.lb(state.dm, instr.pc) } else { val results = returns.map { ret => val puVar = ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts) val entity = (puVar, m) - val eps = ps(entity, StringConstancyProperty.key) + val eps = ps(entity.asInstanceOf[Entity], StringConstancyProperty.key) if (eps.isRefinable) { state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(puVar, defSite) @@ -52,13 +57,31 @@ case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State eps } if (results.exists(_.isRefinable)) { - InterimIPResult.lb + InterimIPResult.lbWithEPSDependees( + state.dm, + instr.pc, + results.filter(_.isRefinable), + awaitAllFinalContinuation( + SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), + finalResult(instr.pc) + ) + ) } else { - FinalIPResult(results.head.asFinal.p.stringConstancyInformation) + finalResult(instr.pc)(results.asInstanceOf[Iterable[SomeFinalEP]]) } } } else { - EmptyIPResult + EmptyIPResult(state.dm, instr.pc) } } + + def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit state: State): FinalIPResult = { + val sci = StringConstancyInformation.reduceMultiple( + results.asInstanceOf[Iterable[EOptionP[_, StringConstancyProperty]]].map( + _.asFinal.p.stringConstancyInformation + ) + ) + + FinalIPResult(sci, state.dm, pc) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 5ef5853998..a789d32e74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -13,7 +13,7 @@ import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.StringInterpreter /** * @author Maximilian Rüsch diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 35ac1272b1..acd436123c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -7,18 +7,16 @@ package string_analysis package l1 package interpretation -import org.opalj.br.ComputationalTypeFloat -import org.opalj.br.ComputationalTypeInt -import org.opalj.br.ObjectType +import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.FinalEP import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.properties.TACAI /** * Responsible for processing [[VirtualFunctionCall]]s with a call graph where applicable. @@ -31,64 +29,26 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider ) extends L0VirtualFunctionCallInterpreter[State](exprHandler) - with L1StringInterpreter[State] { + with L1StringInterpreter[State] + with IPResultDependingStringInterpreter[State] + with EPSDependingStringInterpreter[State] { 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 - * [[L1VirtualFunctionCallInterpreter.interpretReplaceCall]]. - *
      • - *
      • - * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] - * will be returned in case the passed method returns a [[java.lang.String]]. - *
      • - *
      - * - * If none of the above-described cases match, a final result containing - * [[StringConstancyProperty.lb]] is returned. - * - * @note This function takes care of updating [[ComputationState.fpe2sci]] as necessary. - */ - override protected def handleInterpretation(instr: T, defSite: Int)(implicit - state: State - ): IPResult = { - instr.name match { - case "append" => interpretAppendCall(instr) - case "toString" | "replace" => super.handleInterpretation(instr, defSite) - case _ => - instr.descriptor.returnType match { - case obj: ObjectType if obj == ObjectType.String => - interpretArbitraryCall(instr, defSite) - case _ => - super.handleInterpretation(instr, defSite) - } - } - } - /** * This function interprets an arbitrary [[VirtualFunctionCall]]. If this method returns a * [[FinalEP]] 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)( + override protected def interpretArbitraryCall(instr: T, defSite: Int)( implicit state: State ): IPResult = { - val (methods, _) = getMethodsForPC(instr.pc) + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + val (methods, _) = getMethodsForPC(instr.pc) if (methods.isEmpty) { - return FinalIPResult.lb + return FinalIPResult.lb(state.dm, defSitePC) } // TODO: Type Iterator! val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) @@ -101,214 +61,149 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( } }.keys - // 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 params = if (state.nonFinalFunctionArgs.contains(instr)) { - state.nonFinalFunctionArgs(instr) - } else { - evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) - } - // Continue only when all parameter information are available + val params = evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) if (refinableResults.nonEmpty) { state.nonFinalFunctionArgs(instr) = params - return InterimIPResult.lb - } - - state.nonFinalFunctionArgs.remove(instr) - state.nonFinalFunctionArgsPos.remove(instr) - val evaluatedParams = convertEvaluatedParameters(params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal)))) - val results = methods.map { nextMethod => - val (_, tac) = getTACAI(ps, nextMethod, state) - if (tac.isDefined) { - 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 always throws an exception - FinalIPResult.lb - } else { - val results = returns.map { ret => - val entity = - (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts), nextMethod) - StringAnalysis.registerParams(entity, evaluatedParams) - ps(entity, StringConstancyProperty.key) match { - case r: FinalEP[SContext, StringConstancyProperty] => - state.appendToFpe2Sci( - pcOfDefSite(defSite)(state.tac.stmts), - r.p.stringConstancyInformation - ) - FinalIPResult(r.p.stringConstancyInformation) - case eps => - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) - InterimIPResult.lb - } + InterimIPResult.lbWithIPResultDependees( + state.dm, + defSitePC, + refinableResults.asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, instr.pc, state, refinableResults), + (_: Iterable[IPResult]) => { + val params = state.nonFinalFunctionArgs(instr) + state.nonFinalFunctionArgs.remove(instr) + interpretArbitraryCallWithParams(instr, defSite, methods)( + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) + ) } - results.find(_.isRefinable).getOrElse(results.head) - } - } else { - EmptyIPResult - } - } - - if (results.forall(_.isFinal)) { - FinalIPResult(results.head.asFinal.sci) + ) + ) } else { - InterimIPResult.lb - } - } - - /** - * 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])( - implicit state: State - ): IPResult = { - val receiverResults = receiverValuesOfAppendCall(appendCall) - val appendResult = valueOfAppendCall(appendCall) - - if (receiverResults.head.isRefinable || appendResult.isRefinable) { - return InterimIPResult.lb - } - - val receiverScis = receiverResults.map { _.asFinal.sci } - val appendSci = appendResult.asFinal.sci - - // 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 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 (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) { - StringConstancyInformation.reduceMultiple(receiverScis) - } // Receiver and parameter information are available => Combine them - else { - val receiverSci = StringConstancyInformation.reduceMultiple(receiverScis) - StringConstancyInformation( - StringConstancyLevel.determineForConcat( - receiverSci.constancyLevel, - appendSci.constancyLevel - ), - StringConstancyType.APPEND, - receiverSci.possibleStrings + appendSci.possibleStrings + interpretArbitraryCallWithParams(instr, defSite, methods)( + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) ) } - - FinalIPResult(finalSci) } - /** - * 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 [[FinalEP]] - * indicating that all final results for the receiver value are available. - * - * @note All final results computed by this function are put int [[ComputationState.fpe2sci]] even if the returned - * list contains an [[org.opalj.fpcf.InterimResult]]. - */ - private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V] - )(implicit state: State): List[IPResult] = { - val defSites = call.receiver.asVar.definedBy.toArray.sorted - - val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) - val finalResults = allResults.filter(_._2.isFinal) - val finalResultsWithoutNeutralElements = finalResults.filter { - case (_, FinalIPResult(sci)) => sci.isTheNeutralElement - case _ => false + private def interpretArbitraryCallWithParams(instr: T, defSite: Int, methods: Seq[Method])( + params: Seq[Seq[Seq[FinalIPResult]]] + )(implicit state: State): IPResult = { + def finalResult(results: Iterable[IPResult]): FinalIPResult = { + val sci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) + FinalIPResult(sci, state.dm, instr.pc) } - 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.appendToFpe2Sci(next._1, next._2.asFinal.sci) } - - if (intermediateResults.isEmpty) { - finalResults.map(_._2).toList - } else { - List(intermediateResults.head._2) - } - } - - /** - * 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: T)(implicit state: State): IPResult = { - // .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(_)) - - // Defer the computation if there is at least one intermediate result - if (values.exists(_.isRefinable)) { - return InterimIPResult.lb - } + val results = methods.map { m => + val (tacEOptP, calleeTac) = getTACAI(ps, m, state) + + if (tacEOptP.isRefinable) { + InterimIPResult.lbWithEPSDependees( + state.dm, + instr.pc, + Seq(tacEOptP), + awaitAllFinalContinuation( + SimpleEPSDepender(instr, instr.pc, state, Seq(tacEOptP)), + (finalEPs: Iterable[SomeFinalEP]) => + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( + finalEPs.head.ub.asInstanceOf[TACAI].tac.get, + params + ) + ) + ) + } else if (calleeTac.isEmpty) { + // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all + FinalIPResult.lb(state.dm, instr.pc) + } else { + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)(calleeTac.get, params) + } - val sciValues = values.map { _.asFinal.sci } - 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 - val newValueSci = if (defSitesValueSci.isTheNeutralElement) { - if (defSites.head < 0) { - StringConstancyInformation.lb + val returns = calleeTac.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 + FinalIPResult.lb(state.dm, instr.pc) } else { - val ds = state.tac.stmts(defSites.head).asAssignment.targetVar.usedBy.toArray.min - exprHandler.processDefSite(ds) match { - case FinalIPResult(sci) => sci - // Defer the computation if there is no final result yet - case _ => return InterimIPResult.lb + val params = evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) + val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) + if (refinableResults.nonEmpty) { + state.nonFinalFunctionArgs(instr) = params + InterimIPResult.lbWithIPResultDependees( + state.dm, + instr.pc, + refinableResults.asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, instr.pc, state, refinableResults), + (_: Iterable[IPResult]) => { + val params = state.nonFinalFunctionArgs(instr) + state.nonFinalFunctionArgs.remove(instr) + + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( + calleeTac.get, + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) + ) + } + ) + ) + } else { + interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( + calleeTac.get, + params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) + ) } } + } + + if (results.exists(_.isRefinable)) { + InterimIPResult.lbWithIPResultDependees( + state.dm, + instr.pc, + results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + awaitAllFinalContinuation( + SimpleIPResultDepender(instr, instr.pc, state, results), + finalResult _ + ) + ) } else { - defSitesValueSci + finalResult(results) } + } - 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 && - defSitesValueSci.constancyLevel == StringConstancyLevel.CONSTANT - ) { - if (defSitesValueSci.isTheNeutralElement) { - StringConstancyProperty.lb.stringConstancyInformation - } else { - val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => - if (isIntegerValue(sci.possibleStrings)) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) - } else { - sci - } - } - StringConstancyInformation.reduceMultiple(charSciValues) - } - } else { - newValueSci + private def interpretArbitraryCallWithCalleeTACAndParams(instr: T, defSite: Int, calleeMethod: Method)( + calleeTac: TAC, + params: Seq[Seq[Seq[FinalIPResult]]] + )(implicit state: State): IPResult = { + def finalResult(results: Iterable[SomeFinalEP]): FinalIPResult = { + val sci = StringConstancyInformation.reduceMultiple( + results.asInstanceOf[Iterable[FinalEP[_, StringConstancyProperty]]].map { + _.p.stringConstancyInformation } - case ComputationalTypeFloat => - InterpretationHandler.getConstancyInfoForDynamicFloat - // Otherwise, try to compute - case _ => - newValueSci + ) + FinalIPResult(sci, state.dm, instr.pc) } - val e: Integer = defSites.head - state.appendToFpe2Sci(pcOfDefSite(e)(state.tac.stmts), newValueSci, reset = true) - FinalIPResult(finalSci) - } + val evaluatedParams = convertEvaluatedParameters(params) + val returns = calleeTac.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + val results = returns.map { ret => + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.stmts), calleeMethod) + state.appendToVar2IndexMapping(entity._1, defSite) - /** - * Checks whether a given string is an integer value, i.e. contains only numbers. - */ - private def isIntegerValue(toTest: String): Boolean = toTest.forall(_.isDigit) + StringAnalysis.registerParams(entity, evaluatedParams) + ps(entity, StringConstancyProperty.key) + } + if (results.exists(_.isRefinable)) { + InterimIPResult.lbWithEPSDependees( + state.dm, + instr.pc, + results.filter(_.isRefinable), + awaitAllFinalContinuation( + SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), + finalResult _ + ) + ) + } else { + finalResult(results.asInstanceOf[Iterable[SomeFinalEP]]) + } + } } 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 d21355bf5d..17b899f77a 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 @@ -7,7 +7,6 @@ package string_analysis package preprocessing import scala.collection.mutable.ListBuffer -import scala.collection.mutable.Map import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree @@ -16,56 +15,40 @@ 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.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such - * as [[org.opalj.br.fpcf.properties.string_definition.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?`. + * Transforms a [[Path]] into a [[org.opalj.br.fpcf.properties.string_definition.StringTree]]. * - * @param interpretationHandler An concrete instance of [[InterpretationHandler]] that is used to - * process expressions / definition sites. - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -class PathTransformer[State <: ComputationState[State]](val interpretationHandler: InterpretationHandler[State]) { +object PathTransformer { /** * Accumulator function for transforming a path into a StringTree element. */ - private def pathToTreeAcc( - subpath: SubPath, - fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] - )(implicit state: State): Option[StringTree] = { + private def pathToTreeAcc[State <: ComputationState[State]](subpath: SubPath)(implicit + state: State + ): Option[StringTree] = { subpath match { case fpe: FlatPathElement => - val sci = if (fpe2Sci.contains(fpe.pc)) { - StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.pc)) + val sci = if (state.fpe2ipr.contains(fpe.pc) && state.fpe2ipr(fpe.pc).isFinal) { + state.fpe2ipr(fpe.pc).asFinal.sci } else { - val sciToAdd = interpretationHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { - case ValueIPResult(sci) => sci - case NoIPResult => StringConstancyInformation.getNeutralElement - case _ => StringConstancyInformation.lb + state.iHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { + case ValueIPResult(sci) => sci + case _: NoIPResult | _: FallThroughIPResult => StringConstancyInformation.getNeutralElement + case _: EmptyIPResult => StringConstancyInformation.lb } - - fpe2Sci(fpe.pc) = ListBuffer(sciToAdd) - sciToAdd } Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) case npe: NestedPathElement => if (npe.elementType.isDefined) { npe.elementType.get match { case NestedPathType.Repetition => - val processedSubPath = pathToStringTree( - Path(npe.element.toList), - fpe2Sci, - resetExprHandler = false - ) + val processedSubPath = pathToStringTree(Path(npe.element.toList))(state) Some(StringTreeRepetition(processedSubPath)) case _ => - val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne, fpe2Sci) } + val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne)(state) } if (processedSubPaths.nonEmpty) { npe.elementType.get match { case NestedPathType.TryCatchFinally => @@ -96,9 +79,9 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle } else { npe.element.size match { case 0 => None - case 1 => pathToTreeAcc(npe.element.head, fpe2Sci) + case 1 => pathToTreeAcc(npe.element.head)(state) case _ => - val processed = npe.element.flatMap { ne => pathToTreeAcc(ne, fpe2Sci) } + val processed = npe.element.flatMap { ne => pathToTreeAcc(ne)(state) } if (processed.isEmpty) { None } else { @@ -115,32 +98,19 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle * 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.pc]] 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.l0.L0StringAnalysis]] - * 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 * [[org.opalj.br.fpcf.properties.string_definition.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, - fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] = Map.empty, - resetExprHandler: Boolean = true - )(implicit state: State): StringTree = { + def pathToStringTree[State <: ComputationState[State]](path: Path)(implicit state: State): StringTree = { val tree = path.elements.size match { 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(StringConstancyInformation.lb)) + pathToTreeAcc(path.elements.head)(state).getOrElse(StringTreeConst(StringConstancyInformation.lb)) case _ => - val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_, fpe2Sci) }) + val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_)(state) }) if (children.size == 1) { // The concatenation of one child is the child itself children.head @@ -148,9 +118,6 @@ class PathTransformer[State <: ComputationState[State]](val interpretationHandle StringTreeConcat(children) } } - if (resetExprHandler) { - interpretationHandler.reset() - } tree } } From 0d11391dd903d6ffea896a4274cf97472c912a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 29 Feb 2024 13:20:52 +0100 Subject: [PATCH 378/583] Remove fallthrough ip result --- .../analyses/string_analysis/IPResult.scala | 8 -------- .../InterpretationHandler.scala | 19 ++----------------- .../L0VirtualFunctionCallInterpreter.scala | 3 ++- .../preprocessing/PathTransformer.scala | 6 +++--- 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala index 6b047be8bc..33fcd0231c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala @@ -67,14 +67,6 @@ case class NoIPResult(method: DefinedMethod, pc: Int) extends NonRefinableIPResu override def sciOpt: Option[StringConstancyInformation] = None } -case class FallThroughIPResult(method: DefinedMethod, pc: Int, fallThroughPC: Int) extends NonRefinableIPResult { - override def isNoResult: Boolean = true - override def isFallThroughResult: Boolean = true - - override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement, method, pc) - override def sciOpt: Option[StringConstancyInformation] = None -} - sealed trait SomeIPResult extends IPResult { override final def isNoResult: Boolean = false override final def isFallThroughResult: Boolean = false 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 9bd9b14543..e7619039d5 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 @@ -45,14 +45,7 @@ abstract class InterpretationHandler[State <: ComputationState[State]] { val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) if (state.fpe2ipr.contains(defSitePC) && state.fpe2ipr(defSitePC).isFinal) { - if (state.fpe2ipr(defSitePC).isFallThroughResult) { - return processDefSite(valueOriginOfPC( - state.fpe2ipr(defSitePC).asInstanceOf[FallThroughIPResult].fallThroughPC, - state.tac.pcToIndex - ).get) - } else { - return state.fpe2ipr(defSitePC) - } + return state.fpe2ipr(defSitePC) } if (defSite < 0) { @@ -89,15 +82,7 @@ abstract class InterpretationHandler[State <: ComputationState[State]] { } else { val result = processNewDefSite(defSite) state.fpe2ipr(defSitePC) = result - - if (result.isFallThroughResult) { - processDefSite(valueOriginOfPC( - result.asInstanceOf[FallThroughIPResult].fallThroughPC, - state.tac.pcToIndex - ).get) - } else { - result - } + result } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 5edb64bddd..cd1838661b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -298,7 +298,8 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( * the given `toString` is such a function call! Otherwise, the expected behavior cannot be guaranteed. */ private def interpretToStringCall(call: T)(implicit state: State): IPResult = { - FallThroughIPResult(state.dm, call.pc, call.receiver.asVar.definedBy.head) + val ipResult = exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + FinalIPResult(ipResult.sciOpt.get, state.dm, call.pc) } /** 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 17b899f77a..9e5bb58b1d 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 @@ -35,9 +35,9 @@ object PathTransformer { state.fpe2ipr(fpe.pc).asFinal.sci } else { state.iHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { - case ValueIPResult(sci) => sci - case _: NoIPResult | _: FallThroughIPResult => StringConstancyInformation.getNeutralElement - case _: EmptyIPResult => StringConstancyInformation.lb + case ValueIPResult(sci) => sci + case _: NoIPResult => StringConstancyInformation.getNeutralElement + case _: EmptyIPResult => StringConstancyInformation.lb } } Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) From 244ea733d5a780fd2a91ea78d388a94002adc736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 29 Feb 2024 13:39:39 +0100 Subject: [PATCH 379/583] Split interpretation handler from state --- .../string_analysis/ComputationState.scala | 8 +-- .../string_analysis/EPSDepender.scala | 4 +- .../string_analysis/IPResultDepender.scala | 4 +- .../string_analysis/StringAnalysis.scala | 54 +++++++++++-------- .../string_analysis/StringInterpreter.scala | 8 +-- .../BinaryExprInterpreter.scala | 4 +- .../DoubleValueInterpreter.scala | 6 +-- .../FloatValueInterpreter.scala | 4 +- .../IntegerValueInterpreter.scala | 4 +- .../InterpretationHandler.scala | 2 +- .../StringConstInterpreter.scala | 6 +-- .../string_analysis/l0/L0StringAnalysis.scala | 30 ++++++----- .../L0ArrayAccessInterpreter.scala | 2 +- .../L0InterpretationHandler.scala | 4 +- .../L0NonVirtualMethodCallInterpreter.scala | 2 +- .../L0StaticFunctionCallInterpreter.scala | 2 +- .../interpretation/L0StringInterpreter.scala | 2 +- .../L0VirtualFunctionCallInterpreter.scala | 2 +- .../L0VirtualMethodCallInterpreter.scala | 2 +- .../l1/L1ComputationState.scala | 2 +- .../string_analysis/l1/L1StringAnalysis.scala | 47 ++++++++-------- .../L1FieldReadInterpreter.scala | 2 +- .../L1InterpretationHandler.scala | 4 +- .../L1NewArrayInterpreter.scala | 2 +- .../L1NonVirtualFunctionCallInterpreter.scala | 2 +- .../interpretation/L1StringInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 2 +- .../preprocessing/PathTransformer.scala | 25 +++++---- 28 files changed, 125 insertions(+), 113 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 0d91b67ece..b5dd838390 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -16,7 +16,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.properties.TACAI @@ -25,7 +24,7 @@ import org.opalj.tac.fpcf.properties.TACAI * 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. */ -trait ComputationState[State <: ComputationState[State]] { +trait ComputationState { val dm: DefinedMethod /** @@ -38,11 +37,6 @@ trait ComputationState[State <: ComputationState[State]] { */ var tac: TAC = _ - /** - * The interpretation handler to use for computing a final result (if possible). - */ - var iHandler: InterpretationHandler[State] = _ - /** * 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/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala index 4497caf573..d285550618 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala @@ -10,7 +10,7 @@ import org.opalj.fpcf.SomeEOptionP /** * @author Maximilian Rüsch */ -trait EPSDepender[T <: ASTNode[V], State <: ComputationState[State]] { +trait EPSDepender[T <: ASTNode[V], State <: ComputationState] { type Self <: EPSDepender[T, State] def instr: T @@ -21,7 +21,7 @@ trait EPSDepender[T <: ASTNode[V], State <: ComputationState[State]] { def withDependees(newDependees: Seq[SomeEOptionP]): Self } -private[string_analysis] case class SimpleEPSDepender[T <: ASTNode[V], State <: ComputationState[State]]( +private[string_analysis] case class SimpleEPSDepender[T <: ASTNode[V], State <: ComputationState]( override val instr: T, override val pc: Int, override val state: State, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala index b3dcc6e8e7..84520cb519 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala @@ -8,7 +8,7 @@ package string_analysis /** * @author Maximilian Rüsch */ -trait IPResultDepender[T <: ASTNode[V], State <: ComputationState[State]] { +trait IPResultDepender[T <: ASTNode[V], State <: ComputationState] { type Self <: IPResultDepender[T, State] def instr: T @@ -19,7 +19,7 @@ trait IPResultDepender[T <: ASTNode[V], State <: ComputationState[State]] { def withDependees(newDependees: Seq[IPResult]): Self } -private[string_analysis] case class SimpleIPResultDepender[T <: ASTNode[V], State <: ComputationState[State]]( +private[string_analysis] case class SimpleIPResultDepender[T <: ASTNode[V], State <: ComputationState]( override val instr: T, override val pc: Int, override val state: State, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 84436072a5..5d975a2e88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -43,23 +43,26 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringAnalysis extends FPCFAnalysis { - type State <: ComputationState[State] + type State <: ComputationState val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - protected def getInterimResult(state: State): InterimResult[StringConstancyProperty] = InterimResult( + protected def getInterimResult( + state: State, + iHandler: InterpretationHandler[State] + ): InterimResult[StringConstancyProperty] = InterimResult( state.entity, StringConstancyProperty.lb, - computeNewUpperBound(state), + computeNewUpperBound(state, iHandler), state.dependees.toSet, - continuation(state) + continuation(state, iHandler) ) - private def computeNewUpperBound(state: State): StringConstancyProperty = { + private def computeNewUpperBound(state: State, iHandler: InterpretationHandler[State]): StringConstancyProperty = { if (state.computedLeanPath != null) { StringConstancyProperty( PathTransformer - .pathToStringTree(state.computedLeanPath)(state) + .pathToStringTree(state.computedLeanPath)(state, iHandler) .reduce(true) ) } else { @@ -72,7 +75,10 @@ trait StringAnalysis extends FPCFAnalysis { * the possible string values. This method returns either a final [[Result]] or an * [[InterimResult]] depending on whether other information needs to be computed first. */ - protected[string_analysis] def determinePossibleStrings(implicit state: State): ProperPropertyComputationResult + protected[string_analysis] def determinePossibleStrings(implicit + state: State, + iHandler: InterpretationHandler[State] + ): ProperPropertyComputationResult /** * Continuation function for this analysis. @@ -83,7 +89,10 @@ trait StringAnalysis extends FPCFAnalysis { * @return Returns a final result if (already) available. Otherwise, an intermediate result will * be returned. */ - protected[this] def continuation(state: State)(eps: SomeEPS): ProperPropertyComputationResult = { + protected[this] def continuation( + state: State, + iHandler: InterpretationHandler[State] + )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) && @@ -91,7 +100,7 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee.get == eps => state.tac = tac.tac.get state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = state.dependees.filter(_.e != eps.e) @@ -121,7 +130,7 @@ trait StringAnalysis extends FPCFAnalysis { } // Continue only after all necessary function parameters are evaluated if (state.entity2Function.nonEmpty) { - return getInterimResult(state) + return getInterimResult(state, iHandler) } else { // We could try to determine a final result before all function // parameter information are available, however, this will @@ -129,8 +138,8 @@ trait StringAnalysis extends FPCFAnalysis { // defer this computations when we know that all necessary // information are available state.entity2Function.clear() - if (!computeResultsForPath(state.computedLeanPath)(state)) { - return determinePossibleStrings(state) + if (!computeResultsForPath(state.computedLeanPath)(state, iHandler)) { + return determinePossibleStrings(state, iHandler) } } } @@ -139,18 +148,18 @@ trait StringAnalysis extends FPCFAnalysis { state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run if (state.dependees.isEmpty) { - computeFinalResult(state) + computeFinalResult(state, iHandler) } else { - getInterimResult(state) + getInterimResult(state, iHandler) } } else { - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) } case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - getInterimResult(state) + getInterimResult(state, iHandler) case _ => - getInterimResult(state) + getInterimResult(state, iHandler) } } @@ -166,9 +175,9 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - protected def computeFinalResult(state: State): Result = { + protected def computeFinalResult(state: State, iHandler: InterpretationHandler[State]): Result = { val finalSci = PathTransformer - .pathToStringTree(state.computedLeanPath)(state) + .pathToStringTree(state.computedLeanPath)(state, iHandler) .reduce(true) if (state.fpe2iprDependees.nonEmpty) { OPALLogger.logOnce(Warn( @@ -188,12 +197,15 @@ trait StringAnalysis extends FPCFAnalysis { * @param state The current state of the computation. This function will alter [[ComputationState.fpe2ipr]]. * @return Returns `true` if all values computed for the path are final results. */ - protected def computeResultsForPath(p: Path)(implicit state: State): Boolean = { + protected def computeResultsForPath(p: Path)(implicit + state: State, + iHandler: InterpretationHandler[State] + ): Boolean = { var hasFinalResult = true p.elements.foreach { case fpe: FlatPathElement => if (!state.fpe2ipr.contains(fpe.pc) || state.fpe2ipr(fpe.pc).isRefinable) { - val r = state.iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) + val r = iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) state.fpe2ipr(fpe.pc) = r if (r.isRefinable) { hasFinalResult = false diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 8b03b5f6e5..7e70490133 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.properties.TACAI /** * @author Maximilian Rüsch */ -trait StringInterpreter[State <: ComputationState[State]] { +trait StringInterpreter[State <: ComputationState] { type T <: ASTNode[V] @@ -145,7 +145,7 @@ trait StringInterpreter[State <: ComputationState[State]] { }) } -trait SingleStepStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { +trait SingleStepStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { /** * @inheritdoc @@ -156,7 +156,7 @@ trait SingleStepStringInterpreter[State <: ComputationState[State]] extends Stri /** * @author Maximilian Rüsch */ -trait IPResultDependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { +trait IPResultDependingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { protected final def awaitAllFinalContinuation( depender: IPResultDepender[T, State], @@ -194,7 +194,7 @@ trait IPResultDependingStringInterpreter[State <: ComputationState[State]] exten /** * @author Maximilian Rüsch */ -trait EPSDependingStringInterpreter[State <: ComputationState[State]] extends StringInterpreter[State] { +trait EPSDependingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { protected final def awaitAllFinalContinuation( depender: EPSDepender[T, State], 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 ff21412ed5..34ea3e9d09 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 @@ -12,7 +12,7 @@ import org.opalj.br.ComputationalTypeInt /** * @author Maximilian Rüsch */ -case class BinaryExprInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { +case class BinaryExprInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { override type T = BinaryExpr[V] @@ -39,6 +39,6 @@ case class BinaryExprInterpreter[State <: ComputationState[State]]() extends Sin object BinaryExprInterpreter { - def interpret[State <: ComputationState[State]](instr: BinaryExpr[V], defSite: Int)(implicit state: State): IPResult = + def interpret[State <: ComputationState](instr: BinaryExpr[V], defSite: Int)(implicit state: State): IPResult = BinaryExprInterpreter[State]().interpret(instr, defSite) } 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 index c7951211da..6baafbce1c 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/DoubleValueInterpreter.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class DoubleValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { +case class DoubleValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { override type T = DoubleConst @@ -31,8 +31,6 @@ case class DoubleValueInterpreter[State <: ComputationState[State]]() extends Si object DoubleValueInterpreter { - def interpret[State <: ComputationState[State]](instr: DoubleConst, defSite: Int)(implicit - state: State - ): FinalIPResult = + def interpret[State <: ComputationState](instr: DoubleConst, defSite: Int)(implicit state: State): FinalIPResult = DoubleValueInterpreter[State]().interpret(instr, defSite) } 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 index 8a1f0dfe1e..e32031a999 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/FloatValueInterpreter.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class FloatValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { +case class FloatValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { override type T = FloatConst @@ -31,7 +31,7 @@ case class FloatValueInterpreter[State <: ComputationState[State]]() extends Sin object FloatValueInterpreter { - def interpret[State <: ComputationState[State]](instr: FloatConst, defSite: Int)(implicit + def interpret[State <: ComputationState](instr: FloatConst, defSite: Int)(implicit state: State ): FinalIPResult = FloatValueInterpreter[State]().interpret(instr, defSite) 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 88b69ee821..3c72dc05a9 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 @@ -14,7 +14,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType /** * @author Maximilian Rüsch */ -case class IntegerValueInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { +case class IntegerValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { override type T = IntConst @@ -32,6 +32,6 @@ case class IntegerValueInterpreter[State <: ComputationState[State]]() extends S object IntegerValueInterpreter { - def interpret[State <: ComputationState[State]](instr: IntConst, defSite: Int)(implicit state: State): FinalIPResult = + def interpret[State <: ComputationState](instr: IntConst, defSite: Int)(implicit state: State): FinalIPResult = IntegerValueInterpreter[State]().interpret(instr, defSite) } 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 e7619039d5..e2c60b9581 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 @@ -24,7 +24,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType * * @author Maximilian Rüsch */ -abstract class InterpretationHandler[State <: ComputationState[State]] { +abstract class InterpretationHandler[State <: ComputationState] { /** * 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/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala index 61d1db20f1..398e7fc663 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 @@ -14,7 +14,7 @@ import org.opalj.tac.StringConst /** * @author Maximilian Rüsch */ -case class StringConstInterpreter[State <: ComputationState[State]]() extends SingleStepStringInterpreter[State] { +case class StringConstInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { override type T = StringConst @@ -32,8 +32,6 @@ case class StringConstInterpreter[State <: ComputationState[State]]() extends Si object StringConstInterpreter { - def interpret[State <: ComputationState[State]](instr: StringConst, defSite: Int)(implicit - state: State - ): FinalIPResult = + def interpret[State <: ComputationState](instr: StringConst, defSite: Int)(implicit state: State): FinalIPResult = StringConstInterpreter[State]().interpret(instr, defSite) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 86b3ab1805..114ba29e99 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -24,7 +24,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI -trait L0ComputationState[State <: L0ComputationState[State]] extends ComputationState[State] +trait L0ComputationState extends ComputationState /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program @@ -57,18 +57,18 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis protected[l0] case class CState( override val dm: DefinedMethod, override val entity: SContext - ) extends L0ComputationState[CState] + ) extends L0ComputationState override type State = CState def analyze(data: SContext): ProperPropertyComputationResult = { val state = CState(declaredMethods(data._2), data) - state.iHandler = L0InterpretationHandler() + val iHandler = L0InterpretationHandler[CState]() val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { state.tacDependee = Some(tacaiEOptP) - return getInterimResult(state) + return getInterimResult(state, iHandler) } if (tacaiEOptP.ub.tac.isEmpty) { @@ -77,11 +77,12 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } state.tac = tacaiEOptP.ub.tac.get - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) } override protected[string_analysis] def determinePossibleStrings(implicit - state: State + state: State, + iHandler: InterpretationHandler[State] ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac @@ -107,7 +108,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } if (state.parameterDependeesCount > 0) { - return getInterimResult(state) + return getInterimResult(state, iHandler) } if (state.computedLeanPath == null) { @@ -116,7 +117,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - val r = state.iHandler.processDefSite(defSites.head)(state) + val r = iHandler.processDefSite(defSites.head)(state) return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) } @@ -140,9 +141,9 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } if (state.dependees.isEmpty) { - computeFinalResult(state) + computeFinalResult(state, iHandler) } else { - getInterimResult(state) + getInterimResult(state, iHandler) } } @@ -154,17 +155,18 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis * @return This function can either produce a final result or another intermediate result. */ override protected def continuation( - state: State + state: State, + iHandler: InterpretationHandler[State] )(eps: SomeEPS): ProperPropertyComputationResult = eps match { case FinalEP(e: Entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = state.dependees.filter(_.e != e) if (state.dependees.isEmpty) { - computeFinalResult(state) + computeFinalResult(state, iHandler) } else { - getInterimResult(state) + getInterimResult(state, iHandler) } case _ => - super.continuation(state)(eps) + super.continuation(state, iHandler)(eps) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index a3f000611d..5f31a67f49 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -16,7 +16,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0ArrayAccessInterpreter[State <: L0ComputationState[State]]( +case class L0ArrayAccessInterpreter[State <: L0ComputationState]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 6b3c60e053..147f03cb38 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInt * * @author Maximilian Rüsch */ -class L0InterpretationHandler[State <: L0ComputationState[State]]()( +class L0InterpretationHandler[State <: L0ComputationState]()( implicit p: SomeProject, ps: PropertyStore @@ -64,7 +64,7 @@ class L0InterpretationHandler[State <: L0ComputationState[State]]()( object L0InterpretationHandler { - def apply[State <: L0ComputationState[State]]()( + def apply[State <: L0ComputationState]()( implicit p: SomeProject, ps: PropertyStore diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index d4e0078c80..616188b454 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -15,7 +15,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState[State]]( +case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 123f4cd5e5..f86c20c135 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -25,7 +25,7 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter[State <: L0ComputationState[State]]( +case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]( exprHandler: InterpretationHandler[State] )( implicit diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala index e084115f86..298fd85857 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala @@ -10,4 +10,4 @@ package interpretation /** * @author Maximilian Rüsch */ -trait L0StringInterpreter[State <: L0ComputationState[State]] extends StringInterpreter[State] +trait L0StringInterpreter[State <: L0ComputationState] extends StringInterpreter[State] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index cd1838661b..bae956ddab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -26,7 +26,7 @@ import org.opalj.value.TheIntegerValue * * @author Maximilian Rüsch */ -case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState[State]]( +case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( exprHandler: InterpretationHandler[State] ) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index a71c314272..e93d8c1f4f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -16,7 +16,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: L0ComputationState[State]]() +case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends L0StringInterpreter[State] with SingleStepStringInterpreter[State] { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index c95f5d0c41..a4896a0e0a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -13,7 +13,7 @@ import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState -trait L1ComputationState[State <: L1ComputationState[State]] extends L0ComputationState[State] { +trait L1ComputationState extends L0ComputationState { val methodContext: Context diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 6861dca7cf..868e4e3907 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -75,7 +75,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { override val dm: DefinedMethod, override val entity: (SEntity, Method), override val methodContext: Context - ) extends L1ComputationState[CState] + ) extends L1ComputationState override type State = CState @@ -114,12 +114,13 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) - state.iHandler = L1InterpretationHandler(declaredFields, fieldAccessInformation, project, ps, contextProvider) + val iHandler = + L1InterpretationHandler[CState](declaredFields, fieldAccessInformation, project, ps, contextProvider) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { state.tacDependee = Some(tacaiEOptP) - return getInterimResult(state) + return getInterimResult(state, iHandler) } if (tacaiEOptP.ub.tac.isEmpty) { @@ -132,11 +133,11 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val calleesEOptP = ps(dm, Callees.key) if (calleesEOptP.hasNoUBP) { state.calleesDependee = Some(calleesEOptP) - return getInterimResult(state) + return getInterimResult(state, iHandler) } state.callees = calleesEOptP.ub - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) } /** @@ -145,14 +146,15 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. */ override protected[string_analysis] def determinePossibleStrings(implicit - state: State + state: State, + iHandler: InterpretationHandler[State] ): ProperPropertyComputationResult = { val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted if (state.tac == null || state.callees == null) { - return getInterimResult(state) + return getInterimResult(state, iHandler) } val stmts = state.tac.stmts @@ -207,21 +209,21 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub if (!registerParams(state)) { - return getInterimResult(state) + return getInterimResult(state, iHandler) } } else { state.dependees = callersEOptP :: state.dependees - return getInterimResult(state) + return getInterimResult(state, iHandler) } } if (state.parameterDependeesCount > 0) { - return getInterimResult(state) + return getInterimResult(state, iHandler) } // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - val r = state.iHandler.processDefSite(defSites.head)(state) + val r = iHandler.processDefSite(defSites.head)(state) return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) } @@ -239,9 +241,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run if (state.dependees.isEmpty) { - return computeFinalResult(state) + return computeFinalResult(state, iHandler) } else { - return getInterimResult(state) + return getInterimResult(state, iHandler) } case _ => state.dependees = ep :: state.dependees @@ -254,17 +256,17 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val sci = if (attemptFinalResultComputation && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath)(state) + && computeResultsForPath(state.computedLeanPath)(state, iHandler) ) { PathTransformer - .pathToStringTree(state.computedLeanPath)(state) + .pathToStringTree(state.computedLeanPath)(state, iHandler) .reduce(true) } else { StringConstancyInformation.lb } if (state.dependees.nonEmpty) { - getInterimResult(state) + getInterimResult(state, iHandler) } else { StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(sci)) @@ -279,7 +281,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * @return Returns a final result if (already) available. Otherwise, an intermediate result will be returned. */ override protected def continuation( - state: State + state: State, + iHandler: InterpretationHandler[State] )(eps: SomeEPS): ProperPropertyComputationResult = { state.dependees = state.dependees.filter(_.e != eps.e) @@ -287,20 +290,20 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => state.callees = callees if (state.dependees.isEmpty) { - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) } else { - getInterimResult(state) + getInterimResult(state, iHandler) } case FinalP(callers: Callers) if eps.pk.equals(Callers.key) => state.callers = callers if (state.dependees.isEmpty) { registerParams(state) - determinePossibleStrings(state) + determinePossibleStrings(state, iHandler) } else { - getInterimResult(state) + getInterimResult(state, iHandler) } case _ => - super.continuation(state)(eps) + super.continuation(state, iHandler)(eps) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 1a3de2b11e..950ee1d831 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -28,7 +28,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * * @author Maximilian Rüsch */ -case class L1FieldReadInterpreter[State <: L1ComputationState[State]]( +case class L1FieldReadInterpreter[State <: L1ComputationState]( ps: PropertyStore, fieldAccessInformation: FieldAccessInformation, project: SomeProject, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 6c70262350..19f9fc6372 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -28,7 +28,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMe * * @author Maximilian Rüsch */ -class L1InterpretationHandler[State <: L1ComputationState[State]]( +class L1InterpretationHandler[State <: L1ComputationState]( declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, implicit val p: SomeProject, @@ -103,7 +103,7 @@ class L1InterpretationHandler[State <: L1ComputationState[State]]( object L1InterpretationHandler { - def apply[State <: L1ComputationState[State]]( + def apply[State <: L1ComputationState]( declaredFields: DeclaredFields, fieldAccessInformation: FieldAccessInformation, project: SomeProject, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala index 333f851582..a6fbc794fe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala @@ -17,7 +17,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -class L1NewArrayInterpreter[State <: L1ComputationState[State]]( +class L1NewArrayInterpreter[State <: L1ComputationState]( exprHandler: InterpretationHandler[State] ) extends L1StringInterpreter[State] with IPResultDependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 989d3511cd..d7048727f3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.EPSDependingStringInterpreter * * @author Maximilian Rüsch */ -case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState[State]]()( +case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState]()( implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider ) extends L1StringInterpreter[State] with EPSDependingStringInterpreter[State] { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index a789d32e74..962986b535 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -18,7 +18,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.StringInterpreter /** * @author Maximilian Rüsch */ -trait L1StringInterpreter[State <: L1ComputationState[State]] extends StringInterpreter[State] { +trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] { /** * This function returns all methods for a given `pc` among a set of `declaredMethods`. The diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index acd436123c..404963351f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -24,7 +24,7 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Patrick Mell */ -class L1VirtualFunctionCallInterpreter[State <: L1ComputationState[State]]( +class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( exprHandler: InterpretationHandler[State], implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider 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 9e5bb58b1d..4cee6b6ca6 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 @@ -15,6 +15,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.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * Transforms a [[Path]] into a [[org.opalj.br.fpcf.properties.string_definition.StringTree]]. @@ -26,15 +27,16 @@ object PathTransformer { /** * Accumulator function for transforming a path into a StringTree element. */ - private def pathToTreeAcc[State <: ComputationState[State]](subpath: SubPath)(implicit - state: State + private def pathToTreeAcc[State <: ComputationState](subpath: SubPath)(implicit + state: State, + iHandler: InterpretationHandler[State] ): Option[StringTree] = { subpath match { case fpe: FlatPathElement => val sci = if (state.fpe2ipr.contains(fpe.pc) && state.fpe2ipr(fpe.pc).isFinal) { state.fpe2ipr(fpe.pc).asFinal.sci } else { - state.iHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { + iHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { case ValueIPResult(sci) => sci case _: NoIPResult => StringConstancyInformation.getNeutralElement case _: EmptyIPResult => StringConstancyInformation.lb @@ -45,10 +47,10 @@ object PathTransformer { if (npe.elementType.isDefined) { npe.elementType.get match { case NestedPathType.Repetition => - val processedSubPath = pathToStringTree(Path(npe.element.toList))(state) + val processedSubPath = pathToStringTree(Path(npe.element.toList)) Some(StringTreeRepetition(processedSubPath)) case _ => - val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne)(state) } + val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne) } if (processedSubPaths.nonEmpty) { npe.elementType.get match { case NestedPathType.TryCatchFinally => @@ -79,9 +81,9 @@ object PathTransformer { } else { npe.element.size match { case 0 => None - case 1 => pathToTreeAcc(npe.element.head)(state) + case 1 => pathToTreeAcc(npe.element.head) case _ => - val processed = npe.element.flatMap { ne => pathToTreeAcc(ne)(state) } + val processed = npe.element.flatMap { ne => pathToTreeAcc(ne) } if (processed.isEmpty) { None } else { @@ -103,14 +105,17 @@ object PathTransformer { * 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[State <: ComputationState[State]](path: Path)(implicit state: State): StringTree = { + def pathToStringTree[State <: ComputationState](path: Path)(implicit + state: State, + iHandler: InterpretationHandler[State] + ): StringTree = { val tree = path.elements.size match { 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)(state).getOrElse(StringTreeConst(StringConstancyInformation.lb)) + pathToTreeAcc(path.elements.head).getOrElse(StringTreeConst(StringConstancyInformation.lb)) case _ => - val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_)(state) }) + val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_) }) if (children.size == 1) { // The concatenation of one child is the child itself children.head From 15e0e69d58a6109550357e2932c59dcad5ea2939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 8 Mar 2024 12:23:32 +0100 Subject: [PATCH 380/583] Completely revamp interpretation dependency handling --- .../string_analysis/l0/L0TestMethods.java | 14 + .../org/opalj/fpcf/StringAnalysisTest.scala | 2 + .../properties/StringConstancyProperty.scala | 4 +- .../StringConstancyInformation.scala | 56 ++- .../opalj/fpcf/par/PKECPropertyStore.scala | 6 + OPAL/tac/src/main/resources/reference.conf | 5 +- .../string_analysis/ComputationState.scala | 71 --- .../string_analysis/EPSDepender.scala | 27 +- .../analyses/string_analysis/IPResult.scala | 181 ------- .../string_analysis/IPResultDepender.scala | 37 -- .../string_analysis/StringAnalysis.scala | 241 ++++----- .../string_analysis/StringInterpreter.scala | 236 +++------ .../BinaryExprInterpreter.scala | 26 +- .../DoubleValueInterpreter.scala | 17 +- .../FloatValueInterpreter.scala | 15 +- .../IntegerValueInterpreter.scala | 17 +- .../InterpretationHandler.scala | 78 +-- .../StringConstInterpreter.scala | 17 +- .../string_analysis/l0/L0StringAnalysis.scala | 123 ++--- .../L0ArrayAccessInterpreter.scala | 65 ++- .../L0FunctionCallInterpreter.scala | 183 +++++++ .../L0InterpretationHandler.scala | 46 +- .../L0NewArrayInterpreter.scala | 79 +++ .../L0NonVirtualFunctionCallInterpreter.scala | 43 ++ .../L0NonVirtualMethodCallInterpreter.scala | 50 +- .../L0StaticFunctionCallInterpreter.scala | 191 +++---- .../interpretation/L0StringInterpreter.scala | 13 - .../L0VirtualFunctionCallInterpreter.scala | 468 ++++++++++-------- .../L0VirtualMethodCallInterpreter.scala | 19 +- .../l1/L1ComputationState.scala | 6 - .../string_analysis/l1/L1StringAnalysis.scala | 298 ++--------- .../L1FieldReadInterpreter.scala | 138 ++++-- .../L1InterpretationHandler.scala | 96 ++-- .../L1NewArrayInterpreter.scala | 81 --- .../L1NonVirtualFunctionCallInterpreter.scala | 87 ---- .../interpretation/L1StringInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 189 +------ .../preprocessing/PathTransformer.scala | 22 +- .../string_analysis/string_analysis.scala | 23 +- 39 files changed, 1279 insertions(+), 1993 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 43633167d1..6dac43695e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1146,6 +1146,20 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "examples that use a passed parameter to define strings that are analyzed", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*" + ) + }) + public void parameterRead2(String stringValue, StringBuilder sbValue) { + StringBuilder sb = new StringBuilder("value="); + System.out.println(sb.toString()); + sb.append(stringValue); + analyzeString(sb.toString()); + } + @StringDefinitionsCollection( value = "an example extracted from " + "com.oracle.webservices.internal.api.message.BasePropertySet with two " 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 204d74d1de..3ea8e79f79 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 @@ -160,6 +160,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities + // .filter(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // .filter(entity => entity._2.name.startsWith("tryCatchFinally")) // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) @@ -170,6 +171,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) 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 4d30259835..82560f31c8 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 @@ -22,6 +22,8 @@ class StringConstancyProperty( val stringConstancyInformation: StringConstancyInformation ) extends Property with StringConstancyPropertyMetaInformation { + def sci: StringConstancyInformation = stringConstancyInformation + final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { @@ -48,7 +50,7 @@ class StringConstancyProperty( object StringConstancyProperty extends Property with StringConstancyPropertyMetaInformation { - final val PropertyKeyName = "StringConstancy" + final val PropertyKeyName = "opalj.StringConstancy" final val key: PropertyKey[StringConstancyProperty] = { PropertyKey.create( 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 351a6c4a4a..92de3a4d81 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 @@ -8,7 +8,6 @@ package string_definition /** * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this * parameter can be omitted. - * * @author Patrick Mell */ case class StringConstancyInformation( @@ -39,6 +38,36 @@ case class StringConstancyInformation( possibleStrings.contains("?") || possibleStrings.contains("(") || possibleStrings.contains(")") + + def fillInParameters(paramScis: Seq[StringConstancyInformation]): StringConstancyInformation = { + if (possibleStrings.contains( + StringConstancyInformation.ParameterPrefix + paramScis.size + StringConstancyInformation.ParameterSuffix + ) + ) { + throw new IllegalStateException("Insufficient parameters given!") + } + + var strings = possibleStrings + paramScis.zipWithIndex.foreach { + case (sci, index) => + strings = strings.replace( + StringConstancyInformation.ParameterPrefix + index + StringConstancyInformation.ParameterSuffix, + sci.possibleStrings + ) + } + + this.copy(possibleStrings = strings) + } + + def fillInParametersWithLB: StringConstancyInformation = { + val strings = possibleStrings.replaceAll("""\$_\[\d+]""", ".*") + + // IMPROVE enable backwards parse of string constancy information + val level = if (strings == ".*") StringConstancyLevel.DYNAMIC + else StringConstancyLevel.PARTIALLY_CONSTANT + + this.copy(possibleStrings = strings, constancyLevel = level) + } } /** @@ -72,6 +101,20 @@ object StringConstancyInformation { */ val NullStringValue: String = "^null$" + /** + * The prefix given to placeholders representing function parameters. + * + * @see [[getElementForParameterPC]] + */ + val ParameterPrefix: String = "$_[" + + /** + * The suffix given to placeholders representing function parameters. + * + * @see [[getElementForParameterPC]] + */ + val ParameterSuffix: 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 @@ -142,4 +185,15 @@ object StringConstancyInformation { def getNullElement: StringConstancyInformation = StringConstancyInformation(StringConstancyLevel.CONSTANT, possibleStrings = NullStringValue) + def getElementForParameterPC(paramPC: Int): StringConstancyInformation = { + if (paramPC >= -1) { + throw new IllegalArgumentException(s"Invalid parameter pc given: $paramPC") + } + // Parameters start at PC -2 downwards + val paramPosition = Math.abs(paramPC + 2) + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + possibleStrings = ParameterPrefix + paramPosition + ParameterSuffix + ) + } } 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 index 6a2932edd4..9b132c570b 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -4,6 +4,7 @@ package fpcf package par import scala.annotation.switch +import scala.annotation.unused import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.LinkedBlockingQueue @@ -361,6 +362,11 @@ class PKECPropertyStore( ): Unit = { val SomeEPS(e, pk) = interimEP var isFresh = false + + if (interimEP.pk.id == 0 && ps(pk.id).containsKey(e)) { + @unused val a = "" + } + val ePKState = ps(pk.id).computeIfAbsent(e, { _ => isFresh = true; EPKState(interimEP, c, dependees) }) if (isFresh) { diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 3d887fbb74..f2c3e0eb90 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1494,10 +1494,7 @@ org.opalj { }, cg.reflection.ReflectionRelatedCallsAnalysis.highSoundness = "" // e.g. "all" or "class,method", fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, - string_analysis.l1.L1StringAnalysis = { - callersThreshold = 10, - fieldWriteThreshold = 100 - } + string_analysis.l1.L1StringAnalysis.fieldWriteThreshold = 100 } }, tac.cg { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index b5dd838390..7e44e3ef8e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -6,16 +6,11 @@ package fpcf package analyses package string_analysis -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.br.DefinedMethod import org.opalj.br.Method -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property -import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.properties.TACAI @@ -44,74 +39,8 @@ trait ComputationState { var tacDependee: Option[EOptionP[Method, TACAI]] = _ - val fpe2EPSDependees: mutable.Map[Int, List[(EOptionP[Entity, Property], SomeEPS => IPResult)]] = mutable.Map() - val fpe2iprDependees: mutable.Map[Int, (List[IPResult], IPResult => IPResult)] = mutable.Map() - /** * 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 values of the FlatPathElements - */ - val var2IndexMapping: mutable.Map[SEntity, ListBuffer[Int]] = mutable.Map() - - /** - * A mapping from values of FlatPathElements to an interpretation result represented by an [[IPResult]] - */ - val fpe2ipr: mutable.Map[Int, IPResult] = 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 - - /** - * 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[SContext, (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: 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[SContext, ListBuffer[FunctionCall[V]]] = mutable.Map() - - /** - * Takes an entity as well as a pc and append it to [[var2IndexMapping]]. - */ - def appendToVar2IndexMapping(entity: SEntity, pc: Int): Unit = { - if (!var2IndexMapping.contains(entity)) { - var2IndexMapping(entity) = ListBuffer() - } - var2IndexMapping(entity).append(pc) - } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala index d285550618..e0479b82bb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala @@ -10,27 +10,14 @@ import org.opalj.fpcf.SomeEOptionP /** * @author Maximilian Rüsch */ -trait EPSDepender[T <: ASTNode[V], State <: ComputationState] { - type Self <: EPSDepender[T, State] +private[string_analysis] case class EPSDepender[T <: ASTNode[V], State <: ComputationState]( + instr: T, + pc: Int, + state: State, + dependees: Seq[SomeEOptionP] +) { - def instr: T - def pc: Int - def state: State - def dependees: Seq[SomeEOptionP] - - def withDependees(newDependees: Seq[SomeEOptionP]): Self -} - -private[string_analysis] case class SimpleEPSDepender[T <: ASTNode[V], State <: ComputationState]( - override val instr: T, - override val pc: Int, - override val state: State, - override val dependees: Seq[SomeEOptionP] -) extends EPSDepender[T, State] { - - type Self = SimpleEPSDepender[T, State] - - override def withDependees(newDependees: Seq[SomeEOptionP]): SimpleEPSDepender[T, State] = SimpleEPSDepender( + def withDependees(newDependees: Seq[SomeEOptionP]): EPSDepender[T, State] = EPSDepender( instr, pc, state, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala deleted file mode 100644 index 33fcd0231c..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResult.scala +++ /dev/null @@ -1,181 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis - -import org.opalj.br.DefinedMethod -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.SomeEOptionP -import org.opalj.fpcf.SomeEPS - -/** - * A result of an interpretation of some def site using some [[interpretation.InterpretationHandler]]. - * Essentially a simplified version of [[org.opalj.fpcf.EOptionP]] that has been adapted to the needs of string analyses. - * - * @author Maximilian Rüsch - */ -sealed trait IPResult { - def isNoResult: Boolean - def isFallThroughResult: Boolean - - def isFinal: Boolean - final def isRefinable: Boolean = !isFinal - - def asFinal: FinalIPResult - def asNonRefinable: NonRefinableIPResult - def asRefinable: RefinableIPResult - - def e: (DefinedMethod, Int) = (method, pc) - def method: DefinedMethod - def pc: Int - - def sciOpt: Option[StringConstancyInformation] -} - -object IPResult { - def unapply(ipResult: IPResult): Some[(DefinedMethod, Int)] = Some((ipResult.method, ipResult.pc)) -} - -sealed trait NonRefinableIPResult extends IPResult { - override final def isFinal: Boolean = true - override def asNonRefinable: NonRefinableIPResult = this - override def asRefinable: RefinableIPResult = throw new UnsupportedOperationException() -} - -sealed trait RefinableIPResult extends IPResult { - override final def isFinal: Boolean = false - - override final def asFinal: FinalIPResult = throw new UnsupportedOperationException() - override def asNonRefinable: NonRefinableIPResult = throw new UnsupportedOperationException() - override def asRefinable: RefinableIPResult = this -} - -/** - * Indicates that the pc is not relevant to the interpretation of the analyses. - * - * @note Since the pc is not relevant, we are able to return a final result with the neutral element when needed. - * Interpreters handling this result type should either convert it to - * [[StringConstancyInformation.getNeutralElement]] or preserve it. - */ -case class NoIPResult(method: DefinedMethod, pc: Int) extends NonRefinableIPResult { - override def isNoResult: Boolean = true - override def isFallThroughResult: Boolean = false - - override def asFinal: FinalIPResult = FinalIPResult(StringConstancyInformation.getNeutralElement, method, pc) - override def sciOpt: Option[StringConstancyInformation] = None -} - -sealed trait SomeIPResult extends IPResult { - override final def isNoResult: Boolean = false - override final def isFallThroughResult: Boolean = false -} - -case class EmptyIPResult(method: DefinedMethod, pc: Int) extends RefinableIPResult with SomeIPResult { - override def sciOpt: Option[StringConstancyInformation] = None -} - -sealed trait ValueIPResult extends SomeIPResult { - override final def sciOpt: Option[StringConstancyInformation] = Some(sci) - - def sci: StringConstancyInformation -} - -object ValueIPResult { - def unapply(valueIPResult: ValueIPResult): Some[StringConstancyInformation] = Some(valueIPResult.sci) -} - -case class FinalIPResult( - override val sci: StringConstancyInformation, - method: DefinedMethod, - pc: Int -) extends NonRefinableIPResult with ValueIPResult { - override def asFinal: FinalIPResult = this -} - -object FinalIPResult { - def nullElement(method: DefinedMethod, pc: Int) = - new FinalIPResult(StringConstancyInformation.getNullElement, method, pc) - def lb(method: DefinedMethod, pc: Int) = new FinalIPResult(StringConstancyInformation.lb, method, pc) -} - -case class InterimIPResult private ( - override val sci: StringConstancyInformation, - override val method: DefinedMethod, - override val pc: Int, - ipResultDependees: Iterable[RefinableIPResult] = List.empty, - ipResultContinuation: Option[IPResult => IPResult] = None, - epsDependees: Iterable[SomeEOptionP] = List.empty, - epsContinuation: Option[SomeEPS => IPResult] = None -) extends RefinableIPResult with ValueIPResult - -object InterimIPResult { - def fromRefinableIPResults( - sci: StringConstancyInformation, - method: DefinedMethod, - pc: Int, - refinableResults: Iterable[RefinableIPResult], - ipResultContinuation: IPResult => IPResult - ) = new InterimIPResult(sci, method, pc, refinableResults, Some(ipResultContinuation)) - - def lbWithIPResultDependees( - method: DefinedMethod, - pc: Int, - refinableResults: Iterable[RefinableIPResult], - ipResultContinuation: IPResult => IPResult - ) = new InterimIPResult(StringConstancyInformation.lb, method, pc, refinableResults, Some(ipResultContinuation)) - - def fromRefinableEPSResults( - sci: StringConstancyInformation, - method: DefinedMethod, - pc: Int, - epsDependees: Iterable[SomeEOptionP], - epsContinuation: SomeEPS => IPResult - ) = new InterimIPResult( - sci, - method, - pc, - epsDependees = epsDependees, - epsContinuation = Some(epsContinuation) - ) - - def lbWithEPSDependees( - method: DefinedMethod, - pc: Int, - epsDependees: Iterable[SomeEOptionP], - epsContinuation: SomeEPS => IPResult - ) = new InterimIPResult( - StringConstancyInformation.lb, - method, - pc, - epsDependees = epsDependees, - epsContinuation = Some(epsContinuation) - ) - - def unapply(interimIPResult: InterimIPResult): Some[( - StringConstancyInformation, - DefinedMethod, - Int, - Iterable[_], - (_) => IPResult - )] = { - if (interimIPResult.ipResultContinuation.isDefined) { - Some(( - interimIPResult.sci, - interimIPResult.method, - interimIPResult.pc, - interimIPResult.ipResultDependees, - interimIPResult.ipResultContinuation.get - )) - } else { - Some(( - interimIPResult.sci, - interimIPResult.method, - interimIPResult.pc, - interimIPResult.epsDependees, - interimIPResult.epsContinuation.get - )) - } - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala deleted file mode 100644 index 84520cb519..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IPResultDepender.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis - -/** - * @author Maximilian Rüsch - */ -trait IPResultDepender[T <: ASTNode[V], State <: ComputationState] { - type Self <: IPResultDepender[T, State] - - def instr: T - def pc: Int - def state: State - def dependees: Seq[IPResult] - - def withDependees(newDependees: Seq[IPResult]): Self -} - -private[string_analysis] case class SimpleIPResultDepender[T <: ASTNode[V], State <: ComputationState]( - override val instr: T, - override val pc: Int, - override val state: State, - override val dependees: Seq[IPResult] -) extends IPResultDepender[T, State] { - - type Self = SimpleIPResultDepender[T, State] - - override def withDependees(newDependees: Seq[IPResult]): SimpleIPResultDepender[T, State] = SimpleIPResultDepender( - instr, - pc, - state, - newDependees - ) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 5d975a2e88..5c7d6fc990 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -12,20 +12,23 @@ import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +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.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel 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 import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.log.OPALLogger -import org.opalj.log.Warn import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -47,28 +50,7 @@ trait StringAnalysis extends FPCFAnalysis { val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - protected def getInterimResult( - state: State, - iHandler: InterpretationHandler[State] - ): InterimResult[StringConstancyProperty] = InterimResult( - state.entity, - StringConstancyProperty.lb, - computeNewUpperBound(state, iHandler), - state.dependees.toSet, - continuation(state, iHandler) - ) - - private def computeNewUpperBound(state: State, iHandler: InterpretationHandler[State]): StringConstancyProperty = { - if (state.computedLeanPath != null) { - StringConstancyProperty( - PathTransformer - .pathToStringTree(state.computedLeanPath)(state, iHandler) - .reduce(true) - ) - } else { - StringConstancyProperty.lb - } - } + def analyze(data: SContext): ProperPropertyComputationResult /** * Takes the `data` an analysis was started with as well as a computation `state` and determines @@ -102,62 +84,15 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) determinePossibleStrings(state, iHandler) - case FinalEP(entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = state.dependees.filter(_.e != eps.e) - - val e = entity.asInstanceOf[SContext] - // If necessary, update the parameter information with which the surrounding function / method of the entity was called with - if (state.paramResultPositions.contains(e)) { - val pos = state.paramResultPositions(e) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(e) - state.parameterDependeesCount -= 1 - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(e)) { - // Update the state - state.entity2Function(e).foreach { f => - val pos = state.nonFinalFunctionArgsPos(f)(e) - state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = - FinalIPResult(p.stringConstancyInformation, declaredMethods(e._2), pos._3) - // Housekeeping - 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 - if (state.entity2Function.nonEmpty) { - return getInterimResult(state, iHandler) - } 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, iHandler)) { - return determinePossibleStrings(state, iHandler) - } - } - } + case FinalEP(e, _) if eps.pk.equals(StringConstancyProperty.key) => + state.dependees = state.dependees.filter(_.e != e) - if (state.parameterDependeesCount == 0) { - state.dependees = state.dependees.filter(_.e != e) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - computeFinalResult(state, iHandler) - } else { - getInterimResult(state, iHandler) - } + // No more dependees => Return the result for this analysis run + if (state.dependees.isEmpty) { + computeFinalResult(state) } else { - determinePossibleStrings(state, iHandler) + getInterimResult(state, iHandler) } - case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) - if eps.pk.equals(StringConstancyProperty.key) => - getInterimResult(state, iHandler) case _ => getInterimResult(state, iHandler) } @@ -175,44 +110,56 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - protected def computeFinalResult(state: State, iHandler: InterpretationHandler[State]): Result = { + protected def computeFinalResult(state: State): Result = { val finalSci = PathTransformer - .pathToStringTree(state.computedLeanPath)(state, iHandler) + .pathToStringTree(state.computedLeanPath)(state, ps) .reduce(true) - if (state.fpe2iprDependees.nonEmpty) { - OPALLogger.logOnce(Warn( - "string analysis", - "The state still contains IPResult dependees after all EPS dependees were resolved!" - )) - } - StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(finalSci)) } + protected def getInterimResult( + state: State, + iHandler: InterpretationHandler[State] + ): InterimResult[StringConstancyProperty] = { + InterimResult( + state.entity, + StringConstancyProperty.lb, + computeNewUpperBound(state), + state.dependees.toSet, + continuation(state, iHandler) + ) + } + + private def computeNewUpperBound(state: State): StringConstancyProperty = { + if (state.computedLeanPath != null) { + StringConstancyProperty( + PathTransformer + .pathToStringTree(state.computedLeanPath)(state, ps) + .reduce(true) + ) + } else { + StringConstancyProperty.lb + } + } + /** * 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 state The current state of the computation. This function will alter [[ComputationState.fpe2ipr]]. + * @param state The current state of the computation. * @return Returns `true` if all values computed for the path are final results. */ - protected def computeResultsForPath(p: Path)(implicit - state: State, - iHandler: InterpretationHandler[State] - ): Boolean = { + protected def computeResultsForPath(p: Path)(implicit state: State): Boolean = { var hasFinalResult = true p.elements.foreach { case fpe: FlatPathElement => - if (!state.fpe2ipr.contains(fpe.pc) || state.fpe2ipr(fpe.pc).isRefinable) { - val r = iHandler.processDefSite(valueOriginOfPC(fpe.pc, state.tac.pcToIndex).get) - state.fpe2ipr(fpe.pc) = r - if (r.isRefinable) { - hasFinalResult = false - } + val eOptP = ps(InterpretationHandler.getEntityFromDefSitePC(fpe.pc), StringConstancyProperty.key) + if (eOptP.isRefinable) { + hasFinalResult = false } case npe: NestedPathElement => - hasFinalResult = hasFinalResult && computeResultsForPath(Path(npe.element.toList)) + hasFinalResult = hasFinalResult && computeResultsForPath(Path(npe.element.toList))(state) case _ => } @@ -271,27 +218,6 @@ trait StringAnalysis extends FPCFAnalysis { } } - protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { - case duVar: V => duVar.definedBy.exists(_ < 0) - case fc: FunctionCall[V] => fc.params.exists(hasExprFormalParamUsage) - case mc: MethodCall[V] => mc.params.exists(hasExprFormalParamUsage) - case be: BinaryExpr[V] => hasExprFormalParamUsage(be.left) || hasExprFormalParamUsage(be.right) - case _ => false - } - - protected def hasFormalParamUsageAlongPath(path: Path)(implicit tac: TAC): Boolean = { - implicit val pcToIndex: Array[Int] = tac.pcToIndex - path.elements.exists { - case FlatPathElement(index) => tac.stmts(index) match { - case Assignment(_, _, expr) => hasExprFormalParamUsage(expr) - case ExprStmt(_, expr) => hasExprFormalParamUsage(expr) - case _ => false - } - case NestedPathElement(subPath, _) => hasFormalParamUsageAlongPath(Path(subPath.toList)) - case _ => false - } - } - /** * Finds [[PUVar]]s the string constancy information computation for the given [[Path]] depends on. Enables passing * an entity to ignore (usually the entity for which the path was created so it does not depend on itself). @@ -340,33 +266,21 @@ trait StringAnalysis extends FPCFAnalysis { } dependees } -} - -object StringAnalysis { - /** - * 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[ListBuffer[StringConstancyInformation]]]() - - def registerParams(e: Entity, scis: ListBuffer[ListBuffer[StringConstancyInformation]]): Unit = { - if (!paramInfos.contains(e)) { - paramInfos(e) = scis - } else { - paramInfos(e).appendAll(scis) + protected def getPCsInPath(path: Path): Iterable[Int] = { + def getDefSitesOfPathAcc(subpath: SubPath): Iterable[Int] = { + subpath match { + case fpe: FlatPathElement => Seq(fpe.pc) + case npe: NestedPathElement => npe.element.flatMap(getDefSitesOfPathAcc) + case _ => Seq.empty + } } - } - def unregisterParams(e: Entity): Unit = paramInfos.remove(e) + path.elements.flatMap(getDefSitesOfPathAcc) + } +} - def getParams(e: Entity): ListBuffer[ListBuffer[StringConstancyInformation]] = - if (paramInfos.contains(e)) { - paramInfos(e) - } else { - ListBuffer() - } +object StringAnalysis { /** * This function checks whether a given type is a supported primitive type. Supported currently @@ -439,3 +353,44 @@ object StringAnalysis { StringConstancyInformation(StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings) } } + +sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) + + override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.lub(StringConstancyProperty) + ) + + type State <: ComputationState + override final type InitializationData = (StringAnalysis, InterpretationHandler[State]) + + 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 = {} +} + +trait LazyStringAnalysis + extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation( + StringConstancyProperty.key, + (e: Entity) => { + e match { + case _: (_, _) => initData._1.analyze(e.asInstanceOf[SContext]) + case entity: DefSiteEntity => initData._2.analyze(entity) + case _ => throw new IllegalArgumentException(s"Unexpected entity passed for string analysis: $e") + } + } + ) + initData._1 + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 7e70490133..b0455cec42 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -5,18 +5,18 @@ package fpcf package analyses package string_analysis -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -import org.opalj.br.Method +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +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.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.properties.TACAI /** * @author Maximilian Rüsch @@ -26,205 +26,81 @@ trait StringInterpreter[State <: ComputationState] { type T <: ASTNode[V] /** - * @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). - *

      - * As demanded by [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.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, defSite: Int)(implicit state: State): IPResult - - /** - * 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 (eps, tac). + * @param instr The instruction that is to be interpreted. + * @param defSite The definition site that corresponds to the given instruction. + * @return A [[ProperPropertyComputationResult]] for the given def site containing the interpretation of the given + * instruction. */ - protected def getTACAI( - ps: PropertyStore, - m: Method, - s: State - ): (EOptionP[Method, TACAI], Option[TAC]) = { - val tacai = ps(m, TACAI.key) - if (tacai.hasUBP) { - (tacai, tacai.ub.tac) - } else { - (tacai, None) - } - } + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult - /** - * Extracts all parameters of the function calls at the given `pcs`. - */ - protected def getParametersForPCs(pcs: Iterable[Int])(implicit state: State): List[Seq[Expr[V]]] = { - val paramLists = ListBuffer[Seq[Expr[V]]]() - pcs.map(state.tac.pcToIndex).foreach { stmtIndex => - val params = state.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 - } + def computeFinalResult(finalEP: FinalEP[DefSiteEntity, StringConstancyProperty]): Result = + StringInterpreter.computeFinalResult(finalEP) - /** - * evaluateParameters takes a list of parameters, `params`, as produced, e.g., by - * [[StringInterpreter.getParametersForPCs]], and an interpretation handler, `iHandler` - * 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). - * 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: InterpretationHandler[State], - funCall: FunctionCall[V] - )(implicit state: State): NonFinalFunctionArgs = ListBuffer.from(params.zipWithIndex.map { - case (nextParamList, outerIndex) => - ListBuffer.from(nextParamList.zipWithIndex.map { - case (nextParam, middleIndex) => - val e = (nextParam.asVar.toPersistentForm(state.tac.stmts), state.dm.definedMethod) - ListBuffer.from(nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { - case (ds, innerIndex) => - val result = iHandler.processDefSite(ds) - if (result.isRefinable) { - if (!state.nonFinalFunctionArgsPos.contains(funCall)) { - state.nonFinalFunctionArgsPos(funCall) = mutable.Map() - } - state.nonFinalFunctionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) - if (!state.entity2Function.contains(e)) { - state.entity2Function(e) = ListBuffer() - } - state.entity2Function(e).append(funCall) - } - result - }) - }) - }) - - /** - * Checks whether the interpretation of parameters, as, e.g., produced by [[evaluateParameters()]], is final or not - * and returns all refinable results as a list. Hence, an empty list is returned, all parameters are fully evaluated. - */ - protected def getRefinableParameterResults(evaluatedParameters: Seq[Seq[Seq[IPResult]]]): List[IPResult] = - evaluatedParameters.flatten.flatten.filter { _.isRefinable }.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[FinalIPResult]]] - ): ListBuffer[ListBuffer[StringConstancyInformation]] = - ListBuffer.from(evaluatedParameters.map { paramList => - ListBuffer.from(paramList.map { param => StringConstancyInformation.reduceMultiple(param.map { _.sci }) }) - }) -} - -trait SingleStepStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { - - /** - * @inheritdoc - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult -} - -/** - * @author Maximilian Rüsch - */ -trait IPResultDependingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { + def computeFinalResult(defSite: Int, sci: StringConstancyInformation)(implicit state: State): Result = + StringInterpreter.computeFinalResult(defSite, sci) + // IMPROVE remove this since awaiting all final is not really feasible + // replace with intermediate lattice result approach protected final def awaitAllFinalContinuation( - depender: IPResultDepender[T, State], - finalResult: Iterable[IPResult] => IPResult - )(result: IPResult): IPResult = { + depender: EPSDepender[T, State], + finalResult: Iterable[SomeFinalEP] => ProperPropertyComputationResult + )(result: SomeEPS): ProperPropertyComputationResult = { if (result.isFinal) { val updatedDependees = depender.dependees.updated( - depender.dependees.indexWhere(_.e == result.e), + depender.dependees.indexWhere(_.e.asInstanceOf[Entity] == result.e.asInstanceOf[Entity]), result ) if (updatedDependees.forall(_.isFinal)) { - finalResult(updatedDependees) + finalResult(updatedDependees.asInstanceOf[Iterable[SomeFinalEP]]) } else { - InterimIPResult.fromRefinableIPResults( - StringConstancyInformation.lb, - depender.state.dm, - depender.pc, - updatedDependees.filter(_.isRefinable).asInstanceOf[Seq[RefinableIPResult]], + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(depender.pc)(depender.state), + StringConstancyProperty.lb, + depender.dependees.toSet, awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) ) } } else { - InterimIPResult.fromRefinableIPResults( - StringConstancyInformation.lb, - depender.state.dm, - depender.pc, - depender.dependees.asInstanceOf[Seq[RefinableIPResult]], + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(depender.pc)(depender.state), + StringConstancyProperty.lb, + depender.dependees.toSet, awaitAllFinalContinuation(depender, finalResult) ) } } } -/** - * @author Maximilian Rüsch - */ -trait EPSDependingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { +object StringInterpreter { - protected final def awaitAllFinalContinuation( - depender: EPSDepender[T, State], - finalResult: Iterable[SomeFinalEP] => IPResult - )(result: SomeEPS): IPResult = { - if (result.isFinal) { - val updatedDependees = depender.dependees.updated( - depender.dependees.indexWhere(_.e.asInstanceOf[Entity] == result.e.asInstanceOf[Entity]), - result - ) + def computeFinalResult(finalEP: FinalEP[DefSiteEntity, StringConstancyProperty]): Result = Result(finalEP) - if (updatedDependees.forall(_.isFinal)) { - finalResult(updatedDependees.asInstanceOf[Iterable[SomeFinalEP]]) - } else { - InterimIPResult.fromRefinableEPSResults( - StringConstancyInformation.lb, - depender.state.dm, - depender.pc, - updatedDependees.filter(_.isRefinable), - awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) - ) - } - } else { - InterimIPResult.fromRefinableEPSResults( - StringConstancyInformation.lb, - depender.state.dm, - depender.pc, - depender.dependees, - awaitAllFinalContinuation(depender, finalResult) - ) + def computeFinalResult[State <: ComputationState](defSite: Int, sci: StringConstancyInformation)(implicit + state: State + ): Result = + computeFinalResult(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty(sci))) +} + +trait ParameterEvaluatingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { + + val ps: PropertyStore + + protected def getParametersForPC(pc: Int)(implicit state: State): Seq[Expr[V]] = { + state.tac.stmts(state.tac.pcToIndex(pc)) match { + case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params + case Assignment(_, _, fc: FunctionCall[V]) => fc.params + case _ => Seq.empty + } + } + + protected def evaluateParameters(params: Seq[Expr[V]])(implicit + state: State + ): Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = { + params.map { nextParam => + Seq.from(nextParam.asVar.definedBy.toArray.sorted.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + }) } } } 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 34ea3e9d09..3841b77c21 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 @@ -8,11 +8,13 @@ package interpretation import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class BinaryExprInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { +case class BinaryExprInterpreter[State <: ComputationState]() extends StringInterpreter[State] { override type T = BinaryExpr[V] @@ -22,23 +24,21 @@ case class BinaryExprInterpreter[State <: ComputationState]() extends SingleStep *

    • [[ComputationalTypeInt]] *
    • [[ComputationalTypeFloat]]
    • * - * For all other expressions, a [[NoIPResult]] will be returned. + * For all other expressions, a [[StringConstancyInformation.getNeutralElement]] will be returned. */ - def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - instr.cTpe match { - case ComputationalTypeInt => - FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicInt, state.dm, defSitePC) - case ComputationalTypeFloat => - FinalIPResult(InterpretationHandler.getConstancyInfoForDynamicFloat, state.dm, defSitePC) - case _ => - NoIPResult(state.dm, defSitePC) + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + val sci = instr.cTpe match { + case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt + case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat + case _ => StringConstancyInformation.getNeutralElement } + computeFinalResult(defSite, sci) } } object BinaryExprInterpreter { - def interpret[State <: ComputationState](instr: BinaryExpr[V], defSite: Int)(implicit state: State): IPResult = - BinaryExprInterpreter[State]().interpret(instr, defSite) + def interpret[State <: ComputationState](instr: BinaryExpr[V], defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = BinaryExprInterpreter[State]().interpret(instr, defSite) } 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 index 6baafbce1c..546073dd5d 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/DoubleValueInterpreter.scala @@ -9,28 +9,29 @@ package interpretation 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.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class DoubleValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { +case class DoubleValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { override type T = DoubleConst - def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult( + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult( + defSite, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString - ), - state.dm, - pcOfDefSite(defSite)(state.tac.stmts) + ) ) } object DoubleValueInterpreter { - def interpret[State <: ComputationState](instr: DoubleConst, defSite: Int)(implicit state: State): FinalIPResult = - DoubleValueInterpreter[State]().interpret(instr, defSite) + def interpret[State <: ComputationState](instr: DoubleConst, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = DoubleValueInterpreter[State]().interpret(instr, defSite) } 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 index e32031a999..e9a2ff8c77 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/FloatValueInterpreter.scala @@ -9,23 +9,23 @@ package interpretation 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.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class FloatValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { +case class FloatValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { override type T = FloatConst - def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult( + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult( + defSite, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString - ), - state.dm, - pcOfDefSite(defSite)(state.tac.stmts) + ) ) } @@ -33,6 +33,5 @@ object FloatValueInterpreter { def interpret[State <: ComputationState](instr: FloatConst, defSite: Int)(implicit state: State - ): FinalIPResult = - FloatValueInterpreter[State]().interpret(instr, defSite) + ): ProperPropertyComputationResult = FloatValueInterpreter[State]().interpret(instr, defSite) } 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 3c72dc05a9..51569f4127 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 @@ -10,28 +10,29 @@ package interpretation 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.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class IntegerValueInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { +case class IntegerValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { override type T = IntConst - def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult( + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult( + defSite, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString - ), - state.dm, - pcOfDefSite(defSite)(state.tac.stmts) + ) ) } object IntegerValueInterpreter { - def interpret[State <: ComputationState](instr: IntConst, defSite: Int)(implicit state: State): FinalIPResult = - IntegerValueInterpreter[State]().interpret(instr, defSite) + def interpret[State <: ComputationState](instr: IntConst, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = IntegerValueInterpreter[State]().interpret(instr, defSite) } 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 e2c60b9581..15f062569a 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 @@ -9,23 +9,31 @@ package interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset 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.fpcf.FinalEP +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result /** * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site * might have. * * [[InterpretationHandler]]s of any level may use [[StringInterpreter]]s from their level or any level below. - * [[SingleStepStringInterpreter]]s defined in the [[interpretation]] package may be used by any level. + * [[StringInterpreter]]s defined in the [[interpretation]] package may be used by any level. * * @author Maximilian Rüsch */ abstract class InterpretationHandler[State <: ComputationState] { + def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = + processDefSite(valueOriginOfPC(entity.pc, entity.state.tac.pcToIndex).get)(entity.state.asInstanceOf[State]) + /** * Processes a given definition site. That is, this function determines the interpretation of * the specified instruction. @@ -41,66 +49,20 @@ abstract class InterpretationHandler[State <: ComputationState] { * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). * The entity of the result will be the given `defSite`. */ - def processDefSite(defSite: Int)(implicit state: State): IPResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - - if (state.fpe2ipr.contains(defSitePC) && state.fpe2ipr(defSitePC).isFinal) { - return state.fpe2ipr(defSitePC) - } - - if (defSite < 0) { - val params = state.params.toList.map(_.toList) - if (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { - state.fpe2ipr(defSitePC) = FinalIPResult.lb(state.dm, defSitePC) - return FinalIPResult.lb(state.dm, defSitePC) + private def processDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + if (defSite <= FormalParametersOriginOffset) { + if (defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { + return Result(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty.lb)) } else { - val sci = getParam(params, defSite) - state.fpe2ipr(defSitePC) = FinalIPResult(sci, state.dm, defSitePC) - return FinalIPResult(sci, state.dm, defSitePC) + val sci = StringConstancyInformation.getElementForParameterPC(pcOfDefSite(defSite)(state.tac.stmts)) + return Result(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty(sci))) } } - if (state.fpe2iprDependees.contains(defSitePC)) { - val oldDependees = state.fpe2iprDependees(defSitePC) - val updatedDependees = oldDependees._1.map { - case ripr: RefinableIPResult => processDefSite(valueOriginOfPC(ripr.pc, state.tac.pcToIndex).get) - case ipr => ipr - } - if (updatedDependees == oldDependees._1) { - state.fpe2ipr(defSitePC) - } else { - state.fpe2iprDependees(defSitePC) = (updatedDependees, oldDependees._2) - var newResult = state.fpe2ipr(defSitePC) - for { - ipr <- updatedDependees - if !oldDependees._1.contains(ipr) - } { - newResult = oldDependees._2(ipr) - } - newResult - } - } else { - val result = processNewDefSite(defSite) - state.fpe2ipr(defSitePC) = result - result - } + processNewDefSite(defSite) } - protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult - - /** - * 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. - */ - protected def getParam(params: Seq[Seq[StringConstancyInformation]], defSite: Int): StringConstancyInformation = { - val paramPos = Math.abs(defSite + 2) - if (params.exists(_.length <= paramPos)) { - // IMPROVE cant we just map each list of params with a nonexistent pos to lb and still reduce? - StringConstancyInformation.lb - } else { - StringConstancyInformation.reduceMultiple(params.map(_(paramPos)).distinct) - } - } + protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult } object InterpretationHandler { @@ -292,4 +254,10 @@ object InterpretationHandler { StringConstancyType.REPLACE, StringConstancyInformation.UnknownWordSymbol ) + + def getEntityFromDefSite(defSite: Int)(implicit state: ComputationState): DefSiteEntity = + getEntityFromDefSitePC(pcOfDefSite(defSite)(state.tac.stmts)) + + def getEntityFromDefSitePC(defSitePC: Int)(implicit state: ComputationState): DefSiteEntity = + DefSiteEntity(defSitePC, state) } 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 398e7fc663..9b0d5848aa 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 @@ -9,29 +9,30 @@ package interpretation 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.fpcf.ProperPropertyComputationResult import org.opalj.tac.StringConst /** * @author Maximilian Rüsch */ -case class StringConstInterpreter[State <: ComputationState]() extends SingleStepStringInterpreter[State] { +case class StringConstInterpreter[State <: ComputationState]() extends StringInterpreter[State] { override type T = StringConst - def interpret(instr: T, defSite: Int)(implicit state: State): FinalIPResult = - FinalIPResult( + def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult( + defSite, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value - ), - state.dm, - pcOfDefSite(defSite)(state.tac.stmts) + ) ) } object StringConstInterpreter { - def interpret[State <: ComputationState](instr: StringConst, defSite: Int)(implicit state: State): FinalIPResult = - StringConstInterpreter[State]().interpret(instr, defSite) + def interpret[State <: ComputationState](instr: StringConst, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = StringConstInterpreter[State]().interpret(instr, defSite) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 114ba29e99..6cfb31974c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -7,25 +7,21 @@ package string_analysis package l0 import org.opalj.br.DefinedMethod -import org.opalj.br.analyses.ProjectInformationKeys 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.properties.StringConstancyProperty -import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI trait L0ComputationState extends ComputationState +trait L0StringInterpreter[State <: L0ComputationState] extends StringInterpreter[State] + /** * 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''. @@ -61,7 +57,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis override type State = CState - def analyze(data: SContext): ProperPropertyComputationResult = { + override def analyze(data: SContext): ProperPropertyComputationResult = { val state = CState(declaredMethods(data._2), data) val iHandler = L0InterpretationHandler[CState]() @@ -90,11 +86,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis val uVar = puVar.toValueOriginForm(tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted - if (state.params.isEmpty) { - state.params = StringAnalysis.getParams(state.entity) - } - - if (state.params.isEmpty && defSites.exists(_ < 0)) { + if (defSites.exists(_ < 0)) { if (InterpretationHandler.isStringConstExpression(uVar)) { // We can evaluate string const expressions as function parameters } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { @@ -107,32 +99,35 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } } - if (state.parameterDependeesCount > 0) { - return getInterimResult(state, iHandler) + // Interpret a function / method parameter using the parameter information in state + if (defSites.head < 0) { + val ep = ps(InterpretationHandler.getEntityFromDefSite(defSites.head), StringConstancyProperty.key) + if (ep.isRefinable) { + state.dependees = ep :: state.dependees + InterimResult.forLB( + state.entity, + StringConstancyProperty.lb, + state.dependees.toSet, + continuation(state, iHandler) + ) + } else { + return Result(state.entity, StringConstancyProperty(ep.asFinal.p.sci)) + } } if (state.computedLeanPath == null) { state.computedLeanPath = computeLeanPath(uVar) } - // Interpret a function / method parameter using the parameter information in state - if (defSites.head < 0) { - val r = iHandler.processDefSite(defSites.head)(state) - return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) - } - val expr = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, puVar) if (dependentVars.nonEmpty) { - dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } dependentVars.keys.foreach { nextVar => propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { - case FinalEP(e, p) => - // Add mapping information (which will be used for computing the final result) - // state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci(_, p.stringConstancyInformation)) - state.dependees = state.dependees.filter(_.e.asInstanceOf[SContext] != e) + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) case ep => state.dependees = ep :: state.dependees } @@ -140,75 +135,27 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } } + getPCsInPath(state.computedLeanPath).foreach { pc => + propertyStore(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) match { + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) + case ep => + state.dependees = ep :: state.dependees + } + } + if (state.dependees.isEmpty) { - computeFinalResult(state, iHandler) + computeFinalResult(state) } else { getInterimResult(state, iHandler) } } - - /** - * Continuation function. - * - * @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. - */ - override protected def continuation( - state: State, - iHandler: InterpretationHandler[State] - )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalEP(e: Entity, p: StringConstancyProperty) if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = state.dependees.filter(_.e != e) - if (state.dependees.isEmpty) { - computeFinalResult(state, iHandler) - } else { - getInterimResult(state, iHandler) - } - case _ => - super.continuation(state, iHandler)(eps) - } } -sealed trait L0StringAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) - - override final def uses: Set[PropertyBounds] = Set( - PropertyBounds.ub(TACAI), - PropertyBounds.lub(StringConstancyProperty) - ) - - override final type InitializationData = L0StringAnalysis - override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new L0StringAnalysis(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 = {} -} - -object LazyL0StringAnalysis - extends L0StringAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register( - p: SomeProject, - ps: PropertyStore, - analysis: InitializationData - ): FPCFAnalysis = { - val analysis = new L0StringAnalysis(p) - ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) - analysis - } +object LazyL0StringAnalysis extends LazyStringAnalysis { - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + override type State = L0ComputationState - override def requiredProjectInformation: ProjectInformationKeys = Seq(EagerDetachedTACAIKey) + override def init(p: SomeProject, ps: PropertyStore): InitializationData = + (new L0StringAnalysis(p), L0InterpretationHandler()(p, ps)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 5f31a67f49..b5bdc929eb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -7,8 +7,14 @@ package string_analysis package l0 package interpretation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.collection.immutable.IntTrieSet +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -16,63 +22,47 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0ArrayAccessInterpreter[State <: L0ComputationState]( - exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { +case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertyStore) extends L0StringInterpreter[State] { override type T = ArrayLoad[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { implicit val stmts: Array[Stmt[V]] = state.tac.stmts - val defSitePCs = L0ArrayAccessInterpreter.getStoreAndLoadDefSitePCs(instr) - val results = defSitePCs.map { pc => exprHandler.processDefSite(valueOriginOfPC(pc, state.tac.pcToIndex).get) } - - // Add information of parameters - // TODO dont we have to incorporate parameter information into the scis? - instr.arrayRef.asVar.toPersistentForm.defPCs.filter(_ < 0).foreach { pc => - val paramPos = Math.abs(pc + 2) - val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) - val r = FinalIPResult(sci, state.dm, pc) - state.fpe2ipr(pc) = r + val defSitePCs = getStoreAndLoadDefSitePCs(instr) + val results = defSitePCs.map { pc => + ps(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) } - val unfinishedDependees = results.exists(_.isRefinable) - if (unfinishedDependees) { - InterimIPResult.lbWithIPResultDependees( - state.dm, - pcOfDefSite(defSite), - results.filter(_.isRefinable).map(_.asRefinable), + if (results.exists(_.isRefinable)) { + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSite(defSite), + StringConstancyProperty.lb, + results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( - SimpleIPResultDepender(instr, pcOfDefSite(defSite), state, results), + EPSDepender(instr, pcOfDefSite(defSite), state, results), finalResult(pcOfDefSite(defSite)) ) ) } else { - finalResult(pcOfDefSite(defSite))(results) + finalResult(defSite)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } } - private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { - var resultSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) + private def finalResult(defSite: Int)(results: Iterable[SomeFinalEP])(implicit + state: State + ): ProperPropertyComputationResult = { + var resultSci = StringConstancyInformation.reduceMultiple(results.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) if (resultSci.isTheNeutralElement) { resultSci = StringConstancyInformation.lb } - FinalIPResult(resultSci, state.dm, pc) + computeFinalResult(defSite, resultSci) } -} - -object L0ArrayAccessInterpreter { - - type T = ArrayLoad[V] - /** - * This function retrieves all definition sites of the array stores and array loads that belong to the given instruction. - * - * @return All definition sites associated with the array stores and array loads sorted in ascending order. - */ - def getStoreAndLoadDefSitePCs(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { + private def getStoreAndLoadDefSitePCs(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { var defSites = IntTrieSet.empty instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { @@ -86,6 +76,7 @@ object L0ArrayAccessInterpreter { } } - defSites.toList.map(pcOfDefSite(_)).sorted + val allDefSites = defSites ++ instr.arrayRef.asVar.definedBy.toArray.toIndexedSeq.filter(_ < 0) + allDefSites.toList.map(pcOfDefSite(_)).sorted } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala new file mode 100644 index 0000000000..350c2a38c0 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -0,0 +1,183 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EUBP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.TACAI + +/** + * Processes [[NonVirtualFunctionCall]]s without a call graph. + * + * @author Maximilian Rüsch + */ +trait L0FunctionCallInterpreter[State <: L0ComputationState] + extends L0StringInterpreter[State] + with ParameterEvaluatingStringInterpreter[State] { + + override type T <: FunctionCall[V] + + implicit val ps: PropertyStore + + protected[this] case class FunctionCallState( + defSitePC: Int, + calleeMethods: Seq[Method], + var tacDependees: Map[Method, EOptionP[Method, TACAI]], + var returnDependees: Map[Method, Seq[EOptionP[SContext, StringConstancyProperty]]] = Map.empty + ) { + var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) + + private var _paramDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = Seq.empty + private var _paramEntityToPositionMapping: Map[DefSiteEntity, (Int, Int)] = Map.empty + + def paramDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = _paramDependees + + def updateParamDependee(newDependee: EOptionP[DefSiteEntity, StringConstancyProperty]): Unit = { + val pos = _paramEntityToPositionMapping(newDependee.e) + _paramDependees = _paramDependees.updated( + pos._1, + _paramDependees(pos._1).updated( + pos._2, + newDependee + ) + ) + } + + def setParamDependees(newParamDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]]): Unit = { + _paramDependees = newParamDependees + _paramEntityToPositionMapping = Map.empty + _paramDependees.zipWithIndex.map { + case (param, outerIndex) => param.zipWithIndex.map { + case (dependee, innerIndex) => + _paramEntityToPositionMapping += dependee.e -> (outerIndex, innerIndex) + } + } + } + + def updateReturnDependee(method: Method, newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { + returnDependees = returnDependees.updated( + method, + returnDependees(method).updated( + returnDependees(method).indexWhere(_.e == newDependee.e), + newDependee + ) + ) + } + + def hasDependees: Boolean = { + tacDependees.values.exists(_.isRefinable) || + returnDependees.values.flatten.exists(_.isRefinable) || + paramDependees.flatten.exists(_.isRefinable) + } + + def dependees: Iterable[SomeEOptionP] = { + tacDependees.values.filter(_.isRefinable) ++ + returnDependees.values.flatten.filter(_.isRefinable) ++ + paramDependees.flatten.filter(_.isRefinable) + } + } + + protected def interpretArbitraryCallToMethods(implicit + state: State, + callState: FunctionCallState + ): ProperPropertyComputationResult = { + callState.calleeMethods.foreach { m => + val tacEOptP = callState.tacDependees(m) + if (tacEOptP.isFinal) { + val calleeTac = tacEOptP.asFinal.p.tac + if (calleeTac.isEmpty) { + // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all + callState.hasUnresolvableReturnValue += m -> true + } else { + val returns = calleeTac.get.stmts.toIndexedSeq.filter(stmt => stmt.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 + callState.hasUnresolvableReturnValue += m -> true + } else { + callState.returnDependees += m -> returns.map { ret => + val entity: SContext = ( + ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), + m + ) + ps(entity, StringConstancyProperty.key) + } + } + } + } + } + + tryComputeFinalResult + } + + private def tryComputeFinalResult(implicit + state: State, + callState: FunctionCallState + ): ProperPropertyComputationResult = { + if (callState.hasDependees) { + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(callState.defSitePC), + StringConstancyProperty.lb, + callState.dependees.toSet, + continuation(state, callState) + ) + } else { + val parameterScis = callState.paramDependees.map { param => + StringConstancyInformation.reduceMultiple(param.map { + _.asFinal.p.sci + }) + } + val methodScis = callState.calleeMethods.map { m => + if (callState.hasUnresolvableReturnValue(m)) { + StringConstancyInformation.lb + } else { + StringConstancyInformation.reduceMultiple(callState.returnDependees(m).map { + _.asFinal.p.sci.fillInParameters(parameterScis) + }) + } + } + + computeFinalResult(FinalEP( + InterpretationHandler.getEntityFromDefSitePC(callState.defSitePC), + StringConstancyProperty(StringConstancyInformation.reduceMultiple(methodScis)) + )) + } + } + + private def continuation( + state: State, + callState: FunctionCallState + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case EUBP(m: Method, _: TACAI) => + callState.tacDependees += m -> eps.asInstanceOf[EOptionP[Method, TACAI]] + interpretArbitraryCallToMethods(state, callState) + + case EUBP(_: (_, _), _: StringConstancyProperty) => + val contextEPS = eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]] + callState.updateReturnDependee(contextEPS.e._2, contextEPS) + tryComputeFinalResult(state, callState) + + case EUBP(_: DefSiteEntity, _: StringConstancyProperty) => + callState.updateParamDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) + tryComputeFinalResult(state, callState) + + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 147f03cb38..7fa759c579 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -8,6 +8,8 @@ package l0 package interpretation import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter @@ -27,37 +29,43 @@ class L0InterpretationHandler[State <: L0ComputationState]()( ps: PropertyStore ) extends InterpretationHandler[State] { - override protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - + override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { state.tac.stmts(defSite) match { - case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(this).interpret(expr, defSite)(state) - case Assignment(_, _, _: New) => NoIPResult(state.dm, defSitePC) + case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) // Currently unsupported - case Assignment(_, _, _: GetField[V]) => FinalIPResult.lb(state.dm, defSitePC) - case Assignment(_, _, _: NonVirtualFunctionCall[V]) => FinalIPResult.lb(state.dm, defSitePC) + case Assignment(_, _, _: GetField[V]) => + StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.lb) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(ps).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(this).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(ps).interpret(expr, defSite) + + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) + case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, defSite) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) - case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, defSite) + case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, defSite) - case _ => NoIPResult(state.dm, defSitePC) + case _ => + StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala new file mode 100644 index 0000000000..af50317da0 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -0,0 +1,79 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeFinalEP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler + +/** + * Interprets [[NewArray]] expressions without a call graph. + *

      + * + * @author Maximilian Rüsch + */ +class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) + extends L0StringInterpreter[State] { + + override type T = NewArray[V] + + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + if (instr.counts.length != 1) { + // Only supports 1-D arrays + return computeFinalResult(defSite, StringConstancyInformation.lb) + } + + // Get all sites that define array values and process them + val arrValuesDefSites = + state.tac.stmts(defSite).asAssignment.targetVar.asVar.usedBy.toArray.toList.sorted + val allResults = arrValuesDefSites.flatMap { ds => + if (ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[V]]) { + state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + } + } else if (ds < 0) { + Seq(ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key)) + } else { + Seq.empty + } + } + + if (allResults.exists(_.isRefinable)) { + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSite(defSite), + StringConstancyProperty.lb, + allResults.filter(_.isRefinable).toSet, + awaitAllFinalContinuation( + EPSDepender(instr, defSitePC, state, allResults), + finalResult(defSitePC) + ) + ) + } else { + finalResult(defSitePC)(allResults.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + } + } + + private def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit state: State): Result = { + val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) + val sci = if (resultsScis.forall(_.isTheNeutralElement)) { + // It might be that there are no results; in such a case, set the string information to the lower bound + StringConstancyInformation.lb + } else { + StringConstancyInformation.reduceMultiple(resultsScis) + } + + computeFinalResult(valueOriginOfPC(pc, state.tac.pcToIndex).get, sci) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..0cdcd0e9f1 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package l0 +package interpretation + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.properties.TACAI + +/** + * Processes [[NonVirtualFunctionCall]]s without a call graph. + * + * @author Maximilian Rüsch + */ +case class L0NonVirtualFunctionCallInterpreter[State <: L0ComputationState]()( + implicit val p: SomeProject, + implicit val ps: PropertyStore +) extends L0StringInterpreter[State] + with L0FunctionCallInterpreter[State] { + + override type T = NonVirtualFunctionCall[V] + + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) + if (calleeMethod.isEmpty) { + return computeFinalResult(defSite, StringConstancyInformation.lb) + } + + val m = calleeMethod.value + val callState = FunctionCallState(defSite, Seq(m), Map((m, ps(m, TACAI.key)))) + + val params = evaluateParameters(getParametersForPC(pcOfDefSite(defSite)(state.tac.stmts))) + callState.setParamDependees(params) + + interpretArbitraryCallToMethods(state, callState) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 616188b454..94ecd5ff7e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -7,7 +7,14 @@ package string_analysis package l0 package interpretation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.FinalEP +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.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -15,9 +22,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState]( - exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { +case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: PropertyStore) + extends L0StringInterpreter[State] { override type T = NonVirtualMethodCall[V] @@ -33,10 +39,10 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState]( * * For all other calls, a [[NoIPResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr) - case _ => NoIPResult(state.dm, instr.pc) + case _ => computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) } } @@ -46,20 +52,24 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState]( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only these are currently * interpreted). */ - private def interpretInit(init: T)(implicit state: State): IPResult = { + private def interpretInit(init: T)(implicit state: State): ProperPropertyComputationResult = { + val entity = InterpretationHandler.getEntityFromDefSitePC(init.pc) + init.params.size match { - case 0 => NoIPResult(state.dm, init.pc) + case 0 => computeFinalResult(FinalEP(entity, StringConstancyProperty.getNeutralElement)) case _ => - val results = init.params.head.asVar.definedBy.toList.map(exprHandler.processDefSite) + val results = init.params.head.asVar.definedBy.toList.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + } if (results.forall(_.isFinal)) { - finalResult(init.pc)(results) + finalResult(init.pc)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } else { - InterimIPResult.lbWithIPResultDependees( - state.dm, - init.pc, - results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + InterimResult.forLB( + entity, + StringConstancyProperty.lb, + results.toSet, awaitAllFinalContinuation( - SimpleIPResultDepender(init, init.pc, state, results), + EPSDepender(init, init.pc, state, results), finalResult(init.pc) ) ) @@ -67,7 +77,13 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState]( } } - private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { - FinalIPResult(StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)), state.dm, pc) - } + private def finalResult(pc: Int)(results: Iterable[SomeEPS])(implicit + state: State + ): Result = + computeFinalResult(FinalEP( + InterpretationHandler.getEntityFromDefSitePC(pc), + StringConstancyProperty(StringConstancyInformation.reduceMultiple(results.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].sci + })) + )) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index f86c20c135..2f3355e610 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -9,182 +9,107 @@ package interpretation import scala.util.Try -import org.opalj.br.Method import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI /** - * Processes [[StaticFunctionCall]]s in without a call graph. + * Processes [[StaticFunctionCall]]s without a call graph. * * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]( - exprHandler: InterpretationHandler[State] -)( +case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]()( implicit - p: SomeProject, - ps: PropertyStore + override val p: SomeProject, + override val ps: PropertyStore ) extends L0StringInterpreter[State] - with IPResultDependingStringInterpreter[State] - with EPSDependingStringInterpreter[State] { + with L0ArbitraryStaticFunctionCallInterpreter[State] + with L0StringValueOfFunctionCallInterpreter[State] { override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { - if (instr.declaringClass == ObjectType.String && instr.name == "valueOf") { - processStringValueOf(instr) - } else { - processArbitraryCall(instr, defSite) + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + instr.name match { + case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, defSite) + case _ => interpretArbitraryCall(instr, defSite) } } +} - private def processStringValueOf(call: T)(implicit state: State): IPResult = { - def finalResult(results: Iterable[IPResult]): FinalIPResult = { - // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r => r.asFinal.sci } - 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 { - sci - } - } - } else { - scis - } - FinalIPResult(StringConstancyInformation.reduceMultiple(finalScis), state.dm, call.pc) - } +private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter[State <: L0ComputationState] + extends StringInterpreter[State] + with L0FunctionCallInterpreter[State] { - val results = call.params.head.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite) - val hasRefinableResults = results.exists(_.isRefinable) - if (hasRefinableResults) { - InterimIPResult.lbWithIPResultDependees( - state.dm, - call.pc, - results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(call, call.pc, state, results.toIndexedSeq), - finalResult _ - ) - ) - } else { - finalResult(results) - } - } + implicit val p: SomeProject - private def processArbitraryCall(instr: T, defSite: Int)(implicit + override type T = StaticFunctionCall[V] + + def interpretArbitraryCall(instr: T, defSite: Int)(implicit state: State - ): IPResult = { + ): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { - return FinalIPResult.lb(state.dm, instr.pc) + return computeFinalResult(defSite, StringConstancyInformation.lb) } val m = calleeMethod.value - val (tacEOptP, calleeTac) = getTACAI(ps, m, state) + val callState = FunctionCallState(pcOfDefSite(defSite)(state.tac.stmts), Seq(m), Map((m, ps(m, TACAI.key)))) - if (tacEOptP.isRefinable) { - InterimIPResult.lbWithEPSDependees( - state.dm, - instr.pc, - Seq(tacEOptP), - awaitAllFinalContinuation( - SimpleEPSDepender(instr, instr.pc, state, Seq(tacEOptP)), - (finalEPs: Iterable[SomeFinalEP]) => - interpretArbitraryCallWithCalleeTAC(instr, defSite, m)( - finalEPs.head.ub.asInstanceOf[TACAI].tac.get - ) - ) - ) - } else if (calleeTac.isEmpty) { - // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all - FinalIPResult.lb(state.dm, instr.pc) - } else { - interpretArbitraryCallWithCalleeTAC(instr, defSite, m)(calleeTac.get) - } + val params = evaluateParameters(getParametersForPC(pcOfDefSite(defSite)(state.tac.stmts))) + callState.setParamDependees(params) + + interpretArbitraryCallToMethods(state, callState) } +} - private def interpretArbitraryCallWithCalleeTAC(instr: T, defSite: Int, calleeMethod: Method)( - calleeTac: TAC - )(implicit state: State): IPResult = { - val returns = calleeTac.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 - return FinalIPResult.lb(state.dm, instr.pc) - } +private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L0ComputationState] + extends StringInterpreter[State] { - val params = evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) - val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (refinableResults.nonEmpty) { - state.nonFinalFunctionArgs(instr) = params - InterimIPResult.lbWithIPResultDependees( - state.dm, - instr.pc, - refinableResults.asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(instr, instr.pc, state, refinableResults), - (_: Iterable[IPResult]) => { - val params = state.nonFinalFunctionArgs(instr) - state.nonFinalFunctionArgs.remove(instr) - - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, calleeMethod)( - calleeTac, - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - ) - ) - } else { - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, calleeMethod)( - calleeTac, - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - } + override type T <: StaticFunctionCall[V] + + val ps: PropertyStore - private def interpretArbitraryCallWithCalleeTACAndParams(instr: T, defSite: Int, calleeMethod: Method)( - calleeTac: TAC, - params: Seq[Seq[Seq[FinalIPResult]]] - )(implicit state: State): IPResult = { - def finalResult(results: Iterable[SomeFinalEP]): FinalIPResult = { - val sci = StringConstancyInformation.reduceMultiple( - results.asInstanceOf[Iterable[FinalEP[_, StringConstancyProperty]]].map { - _.p.stringConstancyInformation + def processStringValueOf(call: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + def finalResult(results: Iterable[SomeFinalEP]): Result = { + // For char values, we need to do a conversion (as the returned results are integers) + val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } + 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 { + sci + } } - ) - FinalIPResult(sci, state.dm, instr.pc) + } else { + scis + } + computeFinalResult(defSite, StringConstancyInformation.reduceMultiple(finalScis)) } - val evaluatedParams = convertEvaluatedParameters(params) - val returns = calleeTac.stmts.filter(_.isInstanceOf[ReturnValue[V]]) - val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.stmts), calleeMethod) - state.appendToVar2IndexMapping(entity._1, defSite) - - StringAnalysis.registerParams(entity, evaluatedParams) - ps(entity, StringConstancyProperty.key) + val results = call.params.head.asVar.definedBy.toList.sorted.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { - InterimIPResult.lbWithEPSDependees( - state.dm, - instr.pc, - results.filter(_.isRefinable), + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSite(defSite), + StringConstancyProperty.lb, + results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( - SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), - finalResult _ + EPSDepender(call, call.pc, state, results), + finalResult ) ) } else { - finalResult(results.toIndexedSeq.asInstanceOf[Iterable[SomeFinalEP]]) + finalResult(results.asInstanceOf[Iterable[SomeFinalEP]]) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala deleted file mode 100644 index 298fd85857..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StringInterpreter.scala +++ /dev/null @@ -1,13 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l0 -package interpretation - -/** - * @author Maximilian Rüsch - */ -trait L0StringInterpreter[State <: L0ComputationState] extends StringInterpreter[State] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index bae956ddab..0bb4d0474c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -15,9 +15,22 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.DoubleType import org.opalj.br.FloatType 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.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.SomeFinalEP +import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.value.TheIntegerValue @@ -27,8 +40,11 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( - exprHandler: InterpretationHandler[State] -) extends L0StringInterpreter[State] with IPResultDependingStringInterpreter[State] { + override val ps: PropertyStore +) extends L0StringInterpreter[State] + with L0ArbitraryVirtualFunctionCallInterpreter[State] + with L0AppendCallInterpreter[State] + with L0SubstringCallInterpreter[State] { override type T = VirtualFunctionCall[V] @@ -37,7 +53,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( *

        *
      • `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 + * `toString`: Calls to the `toString` function of [[StringBuilder]] and [[StringBuffer]]. As a `toString` call does * not change the state of such an object, an empty list will be returned. *
      • *
      • @@ -52,259 +68,303 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { - case "append" => interpretAppendCall(instr) - case "toString" => interpretToStringCall(instr) - case "replace" => interpretReplaceCall(instr) - case "substring" if instr.descriptor.returnType == ObjectType.String => interpretSubstringCall(instr) + case "append" => interpretAppendCall(instr, defSite) + case "toString" => interpretToStringCall(instr, defSite) + case "replace" => interpretReplaceCall(defSite) + case "substring" if instr.descriptor.returnType == ObjectType.String => + interpretSubstringCall(instr, defSite) case _ => instr.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => interpretArbitraryCall(instr, defSite) - case FloatType | DoubleType => FinalIPResult( + case FloatType | DoubleType => + computeFinalResult( + defSite, StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.FloatValue - ), - state.dm, - instr.pc + ) ) - case _ => NoIPResult(state.dm, instr.pc) + case _ => + computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) } } } - protected def interpretArbitraryCall(call: T, defSite: Int)(implicit state: State): IPResult = - FinalIPResult.lb(state.dm, call.pc) - /** - * 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. + * Processes 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 interpretAppendCall(appendCall: T)(implicit state: State): IPResult = { - def computeFinalAppendCallResult(receiverResults: Iterable[IPResult], appendResult: IPResult): FinalIPResult = { - val receiverScis = receiverResults.map(_.asFinal.sci) - val appendSci = appendResult.asFinal.sci - val areAllReceiversNeutral = receiverScis.forall(_.isTheNeutralElement) - val sci = if (areAllReceiversNeutral && appendSci.isTheNeutralElement) { - // although counter-intuitive, this case occurs if both receiver and parameter have been processed before - StringConstancyInformation.getNeutralElement - } else if (areAllReceiversNeutral) { - // 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 - appendSci - } else if (appendSci.isTheNeutralElement) { - // The append value might be empty, if the site has already been processed (then this - // information will come from another StringConstancyInformation object - StringConstancyInformation.reduceMultiple(receiverScis) - } else { - // Receiver and parameter information are available => combine them - val receiverSci = StringConstancyInformation.reduceMultiple(receiverScis) - StringConstancyInformation( - StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, appendSci.constancyLevel), - StringConstancyType.APPEND, - receiverSci.possibleStrings + appendSci.possibleStrings - ) - } + private def interpretToStringCall(call: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { + eps match { + case FinalP(sciP: StringConstancyProperty) => + computeFinalResult(defSite, sciP.sci) + + case iep: InterimEP[_, _] if eps.pk == StringConstancyProperty.key => + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(call.pc), + iep.lb.asInstanceOf[StringConstancyProperty], + Set(eps), + computeResult + ) + + case _ if eps.pk == StringConstancyProperty.key => + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(call.pc), + StringConstancyProperty.lb, + Set(eps), + computeResult + ) - FinalIPResult(sci, state.dm, appendCall.pc) + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") + } } - val receiverResults = receiverValuesOfCall(appendCall) - val appendResult = valueOfAppendCall(appendCall) + computeResult(ps( + InterpretationHandler.getEntityFromDefSite(call.receiver.asVar.definedBy.head), + StringConstancyProperty.key + )) + } + + /** + * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. + */ + private def interpretReplaceCall(defSite: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult(defSite, InterpretationHandler.getStringConstancyInformationForReplace) +} - if (receiverResults.exists(_.isRefinable) || appendResult.isRefinable) { - val allRefinableResults = if (appendResult.isRefinable) { - receiverResults.filter(_.isRefinable) :+ appendResult - } else receiverResults.filter(_.isRefinable) +private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter[State <: L0ComputationState] + extends L0StringInterpreter[State] { - InterimIPResult.lbWithIPResultDependees( - state.dm, - appendCall.pc, - allRefinableResults.asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(appendCall, appendCall.pc, state, receiverResults :+ appendResult), - (results: Iterable[IPResult]) => { - computeFinalAppendCallResult( - results.filter(_.e != appendResult.e), - results.find(_.e == appendResult.e).get - ) - } + protected def interpretArbitraryCall(call: T, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = + computeFinalResult(defSite, StringConstancyInformation.lb) +} + +/** + * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. + */ +private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationState] + extends L0StringInterpreter[State] { + + override type T = VirtualFunctionCall[V] + + val ps: PropertyStore + + private[this] case class AppendCallState( + appendCall: T, + param: V, + defSitePC: Int, + var receiverDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]], + var valueDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]] + ) { + + def updateDependee(newDependee: EOptionP[DefSiteEntity, StringConstancyProperty]): Unit = { + if (receiverDependees.exists(_.e == newDependee.e)) { + receiverDependees = receiverDependees.updated( + receiverDependees.indexWhere(_.e == newDependee.e), + newDependee ) - ) - } else { - computeFinalAppendCallResult(receiverResults, appendResult) + } else { + valueDependees = valueDependees.updated( + valueDependees.indexWhere(_.e == newDependee.e), + newDependee + ) + } } + + def hasDependees: Boolean = + receiverDependees.exists(_.isRefinable) || valueDependees.exists(_.isRefinable) + + def dependees: Iterable[EOptionP[DefSiteEntity, StringConstancyProperty]] = + receiverDependees.filter(_.isRefinable) ++ valueDependees.filter(_.isRefinable) } - /** - * 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: T)(implicit state: State): IPResult = { + def interpretAppendCall(appendCall: T, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = { + // Get receiver results + val receiverResults = appendCall.receiver.asVar.definedBy.toList.sorted.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + } + // Get parameter results // .head because we want to evaluate only the first argument of append - val param = call.params.head.asVar - val defSites = param.definedBy.toArray.sorted - - def computeFinalAppendValueResult(results: Iterable[IPResult]): FinalIPResult = { - val sciValues = results.map(_.asFinal.sci) - val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) - - val finalSci = param.value.computationalType match { - case ComputationalTypeInt => - if (call.descriptor.parameterType(0).isCharType && - newValueSci.constancyLevel == StringConstancyLevel.CONSTANT && - sciValues.exists(!_.isTheNeutralElement) - ) { - val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => - if (Try(sci.possibleStrings.toInt).isSuccess) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) - } else { - sci - } - } - StringConstancyInformation.reduceMultiple(charSciValues) - } else { - newValueSci - } - case ComputationalTypeFloat | ComputationalTypeDouble => - if (newValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { - newValueSci - } else { - InterpretationHandler.getConstancyInfoForDynamicFloat - } - case _ => - newValueSci + val param = appendCall.params.head.asVar + val valueResults = param.definedBy.toList.sorted.map { ds => + val usedDS = if (ds >= 0 && state.tac.stmts(ds).isAssignment && state.tac.stmts(ds).asAssignment.expr.isNew) { + state.tac.stmts(ds).asAssignment.targetVar.usedBy.toArray.min + } else { + ds } - - FinalIPResult(finalSci, state.dm, call.pc) + ps(InterpretationHandler.getEntityFromDefSite(usedDS), StringConstancyProperty.key) } + implicit val appendState: AppendCallState = + AppendCallState(appendCall, param, pcOfDefSite(defSite)(state.tac.stmts), receiverResults, valueResults) - if (defSites.exists(_ < 0)) { - return FinalIPResult.lb(state.dm, call.pc) - } + tryComputeFinalAppendCallResult + } - val valueResults = defSites.map { ds => - state.tac.stmts(ds) match { - // If a site points to a "New", process the first use site - case Assignment(_, targetVar, _: New) => exprHandler.processDefSite(targetVar.usedBy.toArray.min) - case _ => exprHandler.processDefSite(ds) - } + private def continuation( + state: State, + appendState: AppendCallState + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case UBP(_: StringConstancyProperty) => + appendState.updateDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) + tryComputeFinalAppendCallResult(state, appendState) + + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } + } - // Defer the computation if there is at least one intermediate result - if (valueResults.exists(_.isRefinable)) { - return InterimIPResult.lbWithIPResultDependees( - state.dm, - call.pc, - valueResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(call, call.pc, state, valueResults.toIndexedSeq), - computeFinalAppendValueResult - ) + private def tryComputeFinalAppendCallResult(implicit + state: State, + appendState: AppendCallState + ): ProperPropertyComputationResult = { + if (appendState.hasDependees) { + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(appendState.defSitePC), + StringConstancyProperty.lb, + appendState.dependees.toSet, + continuation(state, appendState) + ) + } else { + val receiverSci = StringConstancyInformation.reduceMultiple(appendState.receiverDependees.map { + _.asFinal.p.sci + }) + val valueSci = transformAppendValueResult( + appendState.valueDependees.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]] ) - } - computeFinalAppendValueResult(valueResults) + computeFinalResult(FinalEP( + InterpretationHandler.getEntityFromDefSitePC(appendState.defSitePC), + StringConstancyProperty(StringConstancyInformation( + StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, valueSci.constancyLevel), + StringConstancyType.APPEND, + receiverSci.possibleStrings + valueSci.possibleStrings + )) + )) + } } - /** - * Processes calls to [[String#substring]]. - */ - private def interpretSubstringCall(substringCall: T)(implicit state: State): IPResult = { - def computeFinalSubstringCallResult(results: Iterable[IPResult]): FinalIPResult = { - val receiverSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - if (receiverSci.isComplex) { - // We cannot yet interpret substrings of mixed values - FinalIPResult.lb(state.dm, substringCall.pc) - } else { - val parameterCount = substringCall.params.size - parameterCount match { - case 1 => - substringCall.params.head.asVar.value match { - case intValue: TheIntegerValue => - FinalIPResult( - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.REPLACE, - receiverSci.possibleStrings.substring(intValue.value) - ), - state.dm, - substringCall.pc - ) - case _ => - FinalIPResult.lb(state.dm, substringCall.pc) - } + private def transformAppendValueResult( + results: Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]] + )(implicit appendState: AppendCallState): StringConstancyInformation = { + val sciValues = results.map(_.p.sci) + val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) - case 2 => - (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { - case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => - FinalIPResult( - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) - ), - state.dm, - substringCall.pc - ) - case _ => - FinalIPResult.lb(state.dm, substringCall.pc) + appendState.param.value.computationalType match { + case ComputationalTypeInt => + if (appendState.appendCall.descriptor.parameterType(0).isCharType && + newValueSci.constancyLevel == StringConstancyLevel.CONSTANT && + sciValues.exists(!_.isTheNeutralElement) + ) { + val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => + if (Try(sci.possibleStrings.toInt).isSuccess) { + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } else { + sci } - - case _ => throw new IllegalStateException( - s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" - ) + } + StringConstancyInformation.reduceMultiple(charSciValues) + } else { + newValueSci } - } + case ComputationalTypeFloat | ComputationalTypeDouble => + if (newValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { + newValueSci + } else { + InterpretationHandler.getConstancyInfoForDynamicFloat + } + case _ => + newValueSci } + } +} - val receiverResults = receiverValuesOfCall(substringCall) - if (receiverResults.forall(_.isNoResult)) { - return FinalIPResult.lb(state.dm, substringCall.pc) +/** + * Interprets calls to [[String#substring]]. + */ +private[string_analysis] trait L0SubstringCallInterpreter[State <: L0ComputationState] + extends L0StringInterpreter[State] { + + override type T = VirtualFunctionCall[V] + + val ps: PropertyStore + + def interpretSubstringCall(substringCall: T, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = { + val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => + ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) } if (receiverResults.exists(_.isRefinable)) { - InterimIPResult.lbWithIPResultDependees( - state.dm, - substringCall.pc, - receiverResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSite(defSite), + StringConstancyProperty.lb, + receiverResults.toSet, awaitAllFinalContinuation( - SimpleIPResultDepender(substringCall, substringCall.pc, state, receiverResults), - computeFinalSubstringCallResult + EPSDepender(substringCall, substringCall.pc, state, receiverResults), + computeFinalSubstringCallResult(substringCall, defSite) ) ) } else { - computeFinalSubstringCallResult(receiverResults) + computeFinalSubstringCallResult(substringCall, defSite)(receiverResults.asInstanceOf[Iterable[SomeFinalEP]]) } } - /** - * This function determines the current value of the receiver object of a call. - */ - private def receiverValuesOfCall(call: T)(implicit state: State): Seq[IPResult] = { - val defSites = call.receiver.asVar.definedBy.toArray.sorted - val allResults = defSites.map(ds => (pcOfDefSite(ds)(state.tac.stmts), exprHandler.processDefSite(ds))) - allResults.foreach { r => state.fpe2ipr(r._1) = r._2 } + private def computeFinalSubstringCallResult(substringCall: T, defSite: Int)( + results: Iterable[SomeFinalEP] + )(implicit state: State): Result = { + val receiverSci = StringConstancyInformation.reduceMultiple(results.map { + _.p.asInstanceOf[StringConstancyProperty].sci + }) + if (receiverSci.isTheNeutralElement || receiverSci.isComplex) { + // We cannot yet interpret substrings of mixed values + computeFinalResult(defSite, StringConstancyInformation.lb) + } else { + val parameterCount = substringCall.params.size + parameterCount match { + case 1 => + substringCall.params.head.asVar.value match { + case intValue: TheIntegerValue => + computeFinalResult( + defSite, + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.REPLACE, + receiverSci.possibleStrings.substring(intValue.value) + ) + ) + case _ => + computeFinalResult(defSite, StringConstancyInformation.lb) + } - allResults.toIndexedSeq.map(_._2) - } + case 2 => + (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { + case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => + computeFinalResult( + defSite, + StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) + ) + ) + case _ => + computeFinalResult(defSite, StringConstancyInformation.lb) + } - /** - * Processes 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: T)(implicit state: State): IPResult = { - val ipResult = exprHandler.processDefSite(call.receiver.asVar.definedBy.head) - FinalIPResult(ipResult.sciOpt.get, state.dm, call.pc) + case _ => throw new IllegalStateException( + s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" + ) + } + } } - - /** - * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. - */ - private def interpretReplaceCall(call: T)(implicit state: State): IPResult = - FinalIPResult(InterpretationHandler.getStringConstancyInformationForReplace, state.dm, call.pc) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index e93d8c1f4f..ba3295152a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -10,14 +10,14 @@ package interpretation 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.fpcf.ProperPropertyComputationResult /** * Responsible for processing [[VirtualMethodCall]]s without a call graph. * * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() - extends L0StringInterpreter[State] with SingleStepStringInterpreter[State] { +case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends L0StringInterpreter[State] { override type T = VirtualMethodCall[V] @@ -32,17 +32,14 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() *
      • *
      * - * For all other calls, a [[NoIPResult]] will be returned. + * For all other calls, a [[StringConstancyInformation.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): NonRefinableIPResult = { - instr.name match { + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + val sci = instr.name match { // IMPROVE interpret argument for setLength - case "setLength" => FinalIPResult( - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET), - state.dm, - instr.pc - ) - case _ => NoIPResult(state.dm, instr.pc) + case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) + case _ => StringConstancyInformation.getNeutralElement } + computeFinalResult(defSite, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index a4896a0e0a..1dac9b9b40 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -9,7 +9,6 @@ package l1 import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState @@ -23,9 +22,4 @@ trait L1ComputationState extends L0ComputationState { * 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: Callers = _ } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 868e4e3907..ad27e94696 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -6,40 +6,25 @@ package analyses package string_analysis package l1 -import scala.collection.mutable.ListBuffer - import org.opalj.br.DefinedMethod -import org.opalj.br.FieldType import org.opalj.br.Method -import org.opalj.br.analyses.DeclaredFields -import org.opalj.br.analyses.DeclaredFieldsKey -import org.opalj.br.analyses.DeclaredMethodsKey -import org.opalj.br.analyses.FieldAccessInformation -import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey -import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.FPCFAnalysisScheduler -import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.log.Error -import org.opalj.log.Info -import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.properties.TACAI @@ -79,43 +64,14 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { override type State = CState - /** - * 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 = { - val threshold = - try { - project.config.getInt(L1StringAnalysis.CallersThresholdConfigKey) - } catch { - case t: Throwable => - logOnce(Error( - "analysis configuration - l1 string analysis", - s"couldn't read: ${L1StringAnalysis.CallersThresholdConfigKey}", - t - )) - 10 - } - - logOnce(Info( - "analysis configuration - l1 string analysis", - "l1 string analysis uses a callers threshold of " + threshold - )) - threshold - } - - protected implicit val declaredFields: DeclaredFields = project.get(DeclaredFieldsKey) - protected implicit val fieldAccessInformation: FieldAccessInformation = project.get(FieldAccessInformationKey) protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) - def analyze(data: SContext): ProperPropertyComputationResult = { + override def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) val iHandler = - L1InterpretationHandler[CState](declaredFields, fieldAccessInformation, project, ps, contextProvider) + L1InterpretationHandler[CState](project, ps) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { @@ -157,98 +113,57 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { return getInterimResult(state, iHandler) } - val stmts = state.tac.stmts - - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar)(state.tac) - } - - var requiresCallersInfo = false - if (state.params.isEmpty) { - state.params = StringAnalysis.getParams(state.entity) - } - 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 if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { - val numType = uVar.value.asPrimitiveValue.primitiveType.toJava - val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) - return Result(state.entity, StringConstancyProperty(sci)) - } else { - // StringBuilders as parameters are currently not evaluated - return Result(state.entity, StringConstancyProperty.lb) - } + if (defSites.exists(_ < 0)) { + if (InterpretationHandler.isStringConstExpression(uVar)) { + // We can evaluate string const expressions as function parameters + } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { + val numType = uVar.value.asPrimitiveValue.primitiveType.toJava + val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) + return Result(state.entity, StringConstancyProperty(sci)) } else { - val call = stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val leanPath = computeLeanPathForStringBuilder(uVar)(state.tac) - if (leanPath.isEmpty) { - return Result(state.entity, StringConstancyProperty.lb) - } - val hasSupportedParamType = state.entity._2.parameterTypes.exists { - StringAnalysis.isSupportedType - } - if (hasSupportedParamType) { - hasFormalParamUsageAlongPath(state.computedLeanPath)(state.tac) - } else { - !hasCallersOrParamInfo - } - } else { - !hasCallersOrParamInfo - } + // StringBuilders as parameters are currently not evaluated + return Result(state.entity, StringConstancyProperty.lb) } } - if (requiresCallersInfo) { - val dm = declaredMethods(state.entity._2) - val callersEOptP = ps(dm, Callers.key) - if (callersEOptP.hasUBP) { - state.callers = callersEOptP.ub - if (!registerParams(state)) { - return getInterimResult(state, iHandler) - } + // Interpret a function / method parameter using the parameter information in state + if (defSites.head < 0) { + val ep = ps(InterpretationHandler.getEntityFromDefSite(defSites.head), StringConstancyProperty.key) + if (ep.isRefinable) { + state.dependees = ep :: state.dependees + InterimResult.forLB( + state.entity, + StringConstancyProperty.lb, + state.dependees.toSet, + continuation(state, iHandler) + ) } else { - state.dependees = callersEOptP :: state.dependees - return getInterimResult(state, iHandler) + return Result(state.entity, ep.asFinal.p) } } - if (state.parameterDependeesCount > 0) { - return getInterimResult(state, iHandler) - } - - // Interpret a function / method parameter using the parameter information in state - if (defSites.head < 0) { - val r = iHandler.processDefSite(defSites.head)(state) - return Result(state.entity, StringConstancyProperty(r.asFinal.sci)) + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uVar)(state.tac) } - val call = stmts(defSites.head).asAssignment.expr + val call = state.tac.stmts(defSites.head).asAssignment.expr var attemptFinalResultComputation = true if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { // Find DUVars that the analysis of the current entity depends on - val dependentVars = findDependentVars(state.computedLeanPath, puVar)(state) - if (dependentVars.nonEmpty) { - dependentVars.keys.foreach { nextVar => - dependentVars.foreach { case (k, v) => state.appendToVar2IndexMapping(k, v) } - val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) - ep match { - case FinalEP(e, p) => - state.dependees = state.dependees.filter(_.e != e) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - return computeFinalResult(state, iHandler) - } else { - return getInterimResult(state, iHandler) - } - case _ => - state.dependees = ep :: state.dependees - attemptFinalResultComputation = false - } + findDependentVars(state.computedLeanPath, puVar)(state).keys.foreach { nextVar => + val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) + ep match { + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) + // No more dependees => Return the result for this analysis run + if (state.dependees.isEmpty) { + return computeFinalResult(state) + } else { + return getInterimResult(state, iHandler) + } + case _ => + state.dependees = ep :: state.dependees + attemptFinalResultComputation = false } } } @@ -256,10 +171,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val sci = if (attemptFinalResultComputation && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath)(state, iHandler) + && computeResultsForPath(state.computedLeanPath)(state) ) { PathTransformer - .pathToStringTree(state.computedLeanPath)(state, iHandler) + .pathToStringTree(state.computedLeanPath)(state, ps) .reduce(true) } else { StringConstancyInformation.lb @@ -268,7 +183,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { if (state.dependees.nonEmpty) { getInterimResult(state, iHandler) } else { - StringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(sci)) } } @@ -294,87 +208,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } else { getInterimResult(state, iHandler) } - case FinalP(callers: Callers) if eps.pk.equals(Callers.key) => - state.callers = callers - if (state.dependees.isEmpty) { - registerParams(state) - determinePossibleStrings(state, iHandler) - } else { - getInterimResult(state, iHandler) - } case _ => super.continuation(state, iHandler)(eps) } } - - /** - * This method takes a computation `state`, and determines the interpretations of all parameters of the method under - * analysis. These interpretations are registered using [[StringAnalysis.registerParams]]. The return value of this - * function indicates whether the parameter evaluation is done (`true`) or not yet (`false`). - */ - private def registerParams(state: State): Boolean = { - val callers = state.callers.callers(state.dm)(contextProvider).iterator.toSeq - if (callers.length > callersThreshold) { - state.params.append( - ListBuffer.from(state.entity._2.parameterTypes.map { - _: FieldType => StringConstancyInformation.lb - }) - ) - return false - } - - var hasIntermediateResult = false - 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 { - 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.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(ListBuffer.from(params.indices.map(_ => - StringConstancyInformation.getNeutralElement - ))) - } - // Recursively analyze supported types - if (StringAnalysis.isSupportedType(p.asVar)) { - val paramEntity = (p.asVar.toPersistentForm(state.tac.stmts), m.definedMethod) - val eps = propertyStore(paramEntity, StringConstancyProperty.key) - state.appendToVar2IndexMapping(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 - } - } - } - // If all parameters could already be determined, register them - if (!hasIntermediateResult) { - StringAnalysis.registerParams(state.entity, state.params) - } - !hasIntermediateResult - } - - override protected def hasExprFormalParamUsage(expr: Expr[V])(implicit tac: TAC): Boolean = expr match { - case al: ArrayLoad[V] => L0ArrayAccessInterpreter.getStoreAndLoadDefSitePCs(al)(tac.stmts).exists(_ < 0) - case _ => super.hasExprFormalParamUsage(expr) - } } object L1StringAnalysis { @@ -382,51 +219,18 @@ object L1StringAnalysis { private[l1] final val FieldWriteThresholdConfigKey = { "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.fieldWriteThreshold" } - - private final val CallersThresholdConfigKey = { - "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.callersThreshold" - } } -sealed trait L1StringAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) - - override final def uses: Set[PropertyBounds] = Set( - PropertyBounds.ub(TACAI), - PropertyBounds.ub(Callees), - PropertyBounds.lub(StringConstancyProperty) - ) - - override final type InitializationData = L1StringAnalysis - override final def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new L1StringAnalysis(p) - } +object LazyL1StringAnalysis extends LazyStringAnalysis { - override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + override type State = L1ComputationState - override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} - - override def afterPhaseCompletion(p: SomeProject, ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} -} - -/** - * Executor for the lazy analysis. - */ -object LazyL1StringAnalysis - extends L1StringAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register(p: SomeProject, ps: PropertyStore, analysis: InitializationData): FPCFAnalysis = { - val analysis = new L1StringAnalysis(p) - ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) - analysis - } + override final def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Callees)) ++ super.uses - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = + (new L1StringAnalysis(p), L1InterpretationHandler(p, ps)) - override def requiredProjectInformation: ProjectInformationKeys = Seq( - DeclaredMethodsKey, - FieldAccessInformationKey, - ContextProviderKey - ) + override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) ++ + L1InterpretationHandler.requiredProjectInformation ++ + super.requiredProjectInformation } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 950ee1d831..4412fb6a5b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -7,8 +7,6 @@ package string_analysis package l1 package interpretation -import scala.collection.mutable.ListBuffer - import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject @@ -16,10 +14,18 @@ import org.opalj.br.fpcf.analyses.ContextProvider 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.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis /** @@ -63,6 +69,25 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( threshold } + private case class FieldReadState( + defSitePC: Int, + var hasInit: Boolean = false, + var hasUnresolvableAccess: Boolean = false, + var accessDependees: Seq[EOptionP[SContext, StringConstancyProperty]] = Seq.empty + ) { + + def updateAccessDependee(newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { + accessDependees = accessDependees.updated( + accessDependees.indexWhere(_.e == newDependee.e), + newDependee + ) + } + + def hasDependees: Boolean = accessDependees.exists(_.isRefinable) + + def dependees: Iterable[SomeEOptionP] = accessDependees.filter(_.isRefinable) + } + /** * Currently, fields are approximated using the following approach: If a field of a type not supported by the * [[L1StringAnalysis]] is passed, [[StringConstancyInformation.lb]] will be produces. Otherwise, all write accesses @@ -70,83 +95,90 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { + override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { - return FinalIPResult.lb(state.dm, defSitePC) + return computeFinalResult(defSite, StringConstancyInformation.lb) } val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { - return FinalIPResult.lb(state.dm, defSitePC) + return computeFinalResult(defSite, StringConstancyInformation.lb) + } + + if (writeAccesses.isEmpty) { + // No methods which write the field were found => Field could either be null or any value + val sci = StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + possibleStrings = + s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" + ) + return computeFinalResult(defSite, sci) } - var hasInit = false - val results = ListBuffer[IPResult]() + implicit val accessState: FieldReadState = FieldReadState(defSitePC) writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod if (method.name == "" || method.name == "") { - hasInit = true + accessState.hasInit = true } - val (tacEps, tac) = getTACAI(ps, method, state) - val nextResult = if (parameter.isEmpty) { + + if (parameter.isEmpty) { // Field parameter information is not available - FinalIPResult.lb(state.dm, defSitePC) - } else if (tacEps.isRefinable) { - EmptyIPResult(state.dm, defSitePC) + accessState.hasUnresolvableAccess = true } else { - tac match { - case Some(_) => - val entity = (PUVar(parameter.get._1, parameter.get._2), method) - val eps = ps(entity, StringConstancyProperty.key) - 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) - EmptyIPResult(state.dm, defSitePC) - } else { - FinalIPResult(eps.asFinal.p.stringConstancyInformation, state.dm, defSitePC) - } - case _ => - // No TAC available - FinalIPResult.lb(state.dm, defSitePC) - } + val entity: SContext = (PUVar(parameter.get._1, parameter.get._2), method) + accessState.accessDependees = accessState.accessDependees :+ ps(entity, StringConstancyProperty.key) } - results.append(nextResult) } - if (results.isEmpty) { - // No methods which write the field were found => Field could either be null or any value - val sci = StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - possibleStrings = - s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" + tryComputeFinalResult + } + + private def tryComputeFinalResult(implicit + state: State, + accessState: FieldReadState + ): ProperPropertyComputationResult = { + if (accessState.hasDependees) { + InterimResult.forLB( + InterpretationHandler.getEntityFromDefSitePC(accessState.defSitePC), + StringConstancyProperty.lb, + accessState.dependees.toSet, + continuation(state, accessState) ) - FinalIPResult(sci, state.dm, defSitePC) } else { - 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(FinalIPResult.nullElement(state.dm, defSitePC)) - } - val finalSci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - FinalIPResult(finalSci, state.dm, defSitePC) - } else { - // IMPROVE return interim result here and depend on EPS - EmptyIPResult(state.dm, defSitePC) + var scis = accessState.accessDependees.map(_.asFinal.p.sci) + // 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 (!accessState.hasInit) { + scis = scis :+ StringConstancyInformation.getNullElement + } + // If an access could not be resolved, append a dynamic element + if (accessState.hasUnresolvableAccess) { + scis = scis :+ StringConstancyInformation.lb } + + computeFinalResult(FinalEP( + InterpretationHandler.getEntityFromDefSitePC(accessState.defSitePC), + StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis)) + )) + } + } + + private def continuation(state: State, accessState: FieldReadState)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case UBP(_: StringConstancyProperty) => + accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) + tryComputeFinalResult(state, accessState) + + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 19f9fc6372..3a5c27dae3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -8,9 +8,15 @@ package l1 package interpretation import org.opalj.br.analyses.DeclaredFields +import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.analyses.FieldAccessInformationKey +import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter @@ -19,6 +25,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueIn import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NewArrayInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMethodCallInterpreter @@ -29,91 +37,71 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMe * @author Maximilian Rüsch */ class L1InterpretationHandler[State <: L1ComputationState]( - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - implicit val p: SomeProject, - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + implicit val p: SomeProject, + implicit val ps: PropertyStore ) extends InterpretationHandler[State] { - override protected def processNewDefSite(defSite: Int)(implicit state: State): IPResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) + val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) + implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) + override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { state.tac.stmts(defSite) match { - case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite)(state) - case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite)(state) // TODO what about long consts + case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite) // TODO what about long const - case Assignment(_, _, expr: ArrayLoad[V]) => - new L0ArrayAccessInterpreter(this).interpret(expr, defSite) - case Assignment(_, _, expr: NewArray[V]) => - new L1NewArrayInterpreter(this).interpret(expr, defSite) + case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) - case Assignment(_, _, _: New) => NoIPResult(state.dm, defSitePC) - - case Assignment(_, _, expr: GetStatic) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( - expr, - defSite - )(state) - case Assignment(_, _, expr: GetField[V]) => + case Assignment(_, _, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( expr, defSite - )(state) - case ExprStmt(_, expr: GetStatic) => + ) + case ExprStmt(_, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( expr, defSite - )(state) - case ExprStmt(_, expr: GetField[V]) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( - expr, - defSite - )(state) + ) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) + new L1VirtualFunctionCallInterpreter().interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter(this, ps, contextProvider).interpret(expr, defSite) + new L1VirtualFunctionCallInterpreter().interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) + L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L1NonVirtualFunctionCallInterpreter().interpret(expr, defSite)(state) + L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter(this).interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, defSite) // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite)(state) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter().interpret(vmc, defSite)(state) + L0VirtualMethodCallInterpreter().interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter(this).interpret(nvmc, defSite) + L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, defSite) - case _ => NoIPResult(state.dm, defSitePC) + case _ => + StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) } } } object L1InterpretationHandler { - def apply[State <: L1ComputationState]( - declaredFields: DeclaredFields, - fieldAccessInformation: FieldAccessInformation, - project: SomeProject, - ps: PropertyStore, - contextProvider: ContextProvider - ): L1InterpretationHandler[State] = new L1InterpretationHandler[State]( - declaredFields, - fieldAccessInformation, - project, - ps, - contextProvider - ) + def requiredProjectInformation: ProjectInformationKeys = + Seq(DeclaredFieldsKey, FieldAccessInformationKey, ContextProviderKey) + + def apply[State <: L1ComputationState](project: SomeProject, ps: PropertyStore): L1InterpretationHandler[State] = + new L1InterpretationHandler[State]()(project, ps) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala deleted file mode 100644 index a6fbc794fe..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NewArrayInterpreter.scala +++ /dev/null @@ -1,81 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.analyses.string_analysis.IPResultDependingStringInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * Interprets [[NewArray]] expressions without a call graph. - *

      - * - * @author Maximilian Rüsch - */ -class L1NewArrayInterpreter[State <: L1ComputationState]( - exprHandler: InterpretationHandler[State] -) extends L1StringInterpreter[State] with IPResultDependingStringInterpreter[State] { - - override type T = NewArray[V] - - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - if (instr.counts.length != 1) { - // Only supports 1-D arrays - return FinalIPResult.lb(state.dm, defSitePC) - } - - // 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 => - exprHandler.processDefSite(d) - } - } - - // Add information of parameters - arrValuesDefSites.filter(_ < 0).foreach { ds => - val paramPos = Math.abs(ds + 2) - // IMPROVE should we use lb as the fallback value - val sci = StringConstancyInformation.reduceMultiple(state.params.map(_(paramPos))) - val r = FinalIPResult(sci, state.dm, pcOfDefSite(ds)(state.tac.stmts)) - state.fpe2ipr(pcOfDefSite(ds)(state.tac.stmts)) = r - allResults ::= r - } - - if (allResults.exists(_.isRefinable)) { - InterimIPResult.lbWithIPResultDependees( - state.dm, - defSitePC, - allResults.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(instr, defSitePC, state, allResults), - finalResult(defSitePC) - ) - ) - } else { - finalResult(defSitePC)(allResults) - } - } - - private def finalResult(pc: Int)(results: Iterable[IPResult])(implicit state: State): FinalIPResult = { - val resultSci = if (results.forall(_.isNoResult)) { - StringConstancyInformation.lb - // 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 - } else { - StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - } - - FinalIPResult(resultSci, state.dm, pc) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala deleted file mode 100644 index d7048727f3..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ /dev/null @@ -1,87 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.EPSDependingStringInterpreter - -/** - * Responsible for processing [[NonVirtualFunctionCall]]s with a call graph. - * - * @author Maximilian Rüsch - */ -case class L1NonVirtualFunctionCallInterpreter[State <: L1ComputationState]()( - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider -) extends L1StringInterpreter[State] with EPSDependingStringInterpreter[State] { - - override type T = NonVirtualFunctionCall[V] - - override def interpret(instr: T, defSite: Int)(implicit state: State): IPResult = { - val methods = getMethodsForPC(instr.pc) - if (methods._1.isEmpty) { - // No methods available => Return lower bound - return FinalIPResult.lb(state.dm, instr.pc) - } - val m = methods._1.head - - val (_, tac) = getTACAI(ps, m, state) - if (tac.isDefined) { - // 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 - FinalIPResult.lb(state.dm, instr.pc) - } else { - val results = returns.map { ret => - val puVar = ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(tac.get.stmts) - val entity = (puVar, m) - - val eps = ps(entity.asInstanceOf[Entity], StringConstancyProperty.key) - if (eps.isRefinable) { - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(puVar, defSite) - } - eps - } - if (results.exists(_.isRefinable)) { - InterimIPResult.lbWithEPSDependees( - state.dm, - instr.pc, - results.filter(_.isRefinable), - awaitAllFinalContinuation( - SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), - finalResult(instr.pc) - ) - ) - } else { - finalResult(instr.pc)(results.asInstanceOf[Iterable[SomeFinalEP]]) - } - } - } else { - EmptyIPResult(state.dm, instr.pc) - } - } - - def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit state: State): FinalIPResult = { - val sci = StringConstancyInformation.reduceMultiple( - results.asInstanceOf[Iterable[EOptionP[_, StringConstancyProperty]]].map( - _.asFinal.p.stringConstancyInformation - ) - ) - - FinalIPResult(sci, state.dm, pc) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index 962986b535..ad4eaee49a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -34,7 +34,7 @@ trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter var hasMethodWithUnknownBody = false val methods = ListBuffer[Method]() - state.callees.callees(state.methodContext, pc)(ps, contextProvider).map(_.method).foreach { + state.callees.callees(state.methodContext, pc).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => hasMethodWithUnknownBody = true } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 404963351f..e0162a53fe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -7,203 +7,46 @@ package string_analysis package l1 package interpretation -import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI /** - * Responsible for processing [[VirtualFunctionCall]]s with a call graph where applicable. - * The list of currently supported function calls can be seen in the documentation of [[interpret]]. + * Processes [[VirtualFunctionCall]]s similar to the [[L0VirtualFunctionCallInterpreter]] but handles arbitrary calls + * with a call graph. * - * @author Patrick Mell + * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( - exprHandler: InterpretationHandler[State], - implicit val ps: PropertyStore, + override implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider -) extends L0VirtualFunctionCallInterpreter[State](exprHandler) +) extends L0VirtualFunctionCallInterpreter[State](ps) with L1StringInterpreter[State] - with IPResultDependingStringInterpreter[State] - with EPSDependingStringInterpreter[State] { + with L0FunctionCallInterpreter[State] { override type T = VirtualFunctionCall[V] - /** - * This function interprets an arbitrary [[VirtualFunctionCall]]. If this method returns a - * [[FinalEP]] 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. - */ override protected def interpretArbitraryCall(instr: T, defSite: Int)( implicit state: State - ): IPResult = { + ): ProperPropertyComputationResult = { val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + // IMPROVE add some uncertainty element if methods with unknown body exist val (methods, _) = getMethodsForPC(instr.pc) if (methods.isEmpty) { - return FinalIPResult.lb(state.dm, defSitePC) + return computeFinalResult(defSite, StringConstancyInformation.lb) } - // TODO: Type Iterator! - val directCallSites = state.callees.directCallSites(state.methodContext)(ps, contextProvider) - val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava - val relevantPCs = directCallSites.filter { - case (_, calledMethods) => calledMethods.exists { m => - val mClassName = m.method.declaringClassType.toJava - m.method.name == instr.name && mClassName == instrClassName - } - }.keys + val params = evaluateParameters(getParametersForPC(defSitePC)) + val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val params = evaluateParameters(getParametersForPCs(relevantPCs), exprHandler, instr) - val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (refinableResults.nonEmpty) { - state.nonFinalFunctionArgs(instr) = params - InterimIPResult.lbWithIPResultDependees( - state.dm, - defSitePC, - refinableResults.asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(instr, instr.pc, state, refinableResults), - (_: Iterable[IPResult]) => { - val params = state.nonFinalFunctionArgs(instr) - state.nonFinalFunctionArgs.remove(instr) - interpretArbitraryCallWithParams(instr, defSite, methods)( - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - ) - ) - } else { - interpretArbitraryCallWithParams(instr, defSite, methods)( - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - } - - private def interpretArbitraryCallWithParams(instr: T, defSite: Int, methods: Seq[Method])( - params: Seq[Seq[Seq[FinalIPResult]]] - )(implicit state: State): IPResult = { - def finalResult(results: Iterable[IPResult]): FinalIPResult = { - val sci = StringConstancyInformation.reduceMultiple(results.map(_.asFinal.sci)) - FinalIPResult(sci, state.dm, instr.pc) - } - - val results = methods.map { m => - val (tacEOptP, calleeTac) = getTACAI(ps, m, state) - - if (tacEOptP.isRefinable) { - InterimIPResult.lbWithEPSDependees( - state.dm, - instr.pc, - Seq(tacEOptP), - awaitAllFinalContinuation( - SimpleEPSDepender(instr, instr.pc, state, Seq(tacEOptP)), - (finalEPs: Iterable[SomeFinalEP]) => - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( - finalEPs.head.ub.asInstanceOf[TACAI].tac.get, - params - ) - ) - ) - } else if (calleeTac.isEmpty) { - // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all - FinalIPResult.lb(state.dm, instr.pc) - } else { - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)(calleeTac.get, params) - } - - val returns = calleeTac.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 - FinalIPResult.lb(state.dm, instr.pc) - } else { - val params = evaluateParameters(getParametersForPCs(List(instr.pc)), exprHandler, instr) - val refinableResults = getRefinableParameterResults(params.toSeq.map(t => t.toSeq.map(_.toSeq))) - if (refinableResults.nonEmpty) { - state.nonFinalFunctionArgs(instr) = params - InterimIPResult.lbWithIPResultDependees( - state.dm, - instr.pc, - refinableResults.asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(instr, instr.pc, state, refinableResults), - (_: Iterable[IPResult]) => { - val params = state.nonFinalFunctionArgs(instr) - state.nonFinalFunctionArgs.remove(instr) - - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( - calleeTac.get, - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - ) - ) - } else { - interpretArbitraryCallWithCalleeTACAndParams(instr, defSite, m)( - calleeTac.get, - params.toSeq.map(t => t.toSeq.map(_.toSeq.map(_.asFinal))) - ) - } - } - } + val callState = FunctionCallState(defSitePC, tacDependees.keys.toSeq, tacDependees) + callState.setParamDependees(params) - if (results.exists(_.isRefinable)) { - InterimIPResult.lbWithIPResultDependees( - state.dm, - instr.pc, - results.filter(_.isRefinable).asInstanceOf[Iterable[RefinableIPResult]], - awaitAllFinalContinuation( - SimpleIPResultDepender(instr, instr.pc, state, results), - finalResult _ - ) - ) - } else { - finalResult(results) - } - } - - private def interpretArbitraryCallWithCalleeTACAndParams(instr: T, defSite: Int, calleeMethod: Method)( - calleeTac: TAC, - params: Seq[Seq[Seq[FinalIPResult]]] - )(implicit state: State): IPResult = { - def finalResult(results: Iterable[SomeFinalEP]): FinalIPResult = { - val sci = StringConstancyInformation.reduceMultiple( - results.asInstanceOf[Iterable[FinalEP[_, StringConstancyProperty]]].map { - _.p.stringConstancyInformation - } - ) - FinalIPResult(sci, state.dm, instr.pc) - } - - val evaluatedParams = convertEvaluatedParameters(params) - val returns = calleeTac.stmts.filter(_.isInstanceOf[ReturnValue[V]]) - val results = returns.map { ret => - val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.stmts), calleeMethod) - state.appendToVar2IndexMapping(entity._1, defSite) - - StringAnalysis.registerParams(entity, evaluatedParams) - ps(entity, StringConstancyProperty.key) - } - if (results.exists(_.isRefinable)) { - InterimIPResult.lbWithEPSDependees( - state.dm, - instr.pc, - results.filter(_.isRefinable), - awaitAllFinalContinuation( - SimpleEPSDepender(instr, instr.pc, state, results.toIndexedSeq), - finalResult _ - ) - ) - } else { - finalResult(results.asInstanceOf[Iterable[SomeFinalEP]]) - } + interpretArbitraryCallToMethods(state, callState) } } 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 4cee6b6ca6..8f388a31bd 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 @@ -8,6 +8,7 @@ package preprocessing import scala.collection.mutable.ListBuffer +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTree import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -15,6 +16,8 @@ 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.fpcf.FinalP +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -28,19 +31,14 @@ object PathTransformer { * Accumulator function for transforming a path into a StringTree element. */ private def pathToTreeAcc[State <: ComputationState](subpath: SubPath)(implicit - state: State, - iHandler: InterpretationHandler[State] + state: State, + ps: PropertyStore ): Option[StringTree] = { subpath match { case fpe: FlatPathElement => - val sci = if (state.fpe2ipr.contains(fpe.pc) && state.fpe2ipr(fpe.pc).isFinal) { - state.fpe2ipr(fpe.pc).asFinal.sci - } else { - iHandler.processDefSite(fpe.stmtIndex(state.tac.pcToIndex)) match { - case ValueIPResult(sci) => sci - case _: NoIPResult => StringConstancyInformation.getNeutralElement - case _: EmptyIPResult => StringConstancyInformation.lb - } + val sci = ps(InterpretationHandler.getEntityFromDefSitePC(fpe.pc), StringConstancyProperty.key) match { + case FinalP(scp) => scp.sci + case _ => StringConstancyInformation.lb } Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) case npe: NestedPathElement => @@ -106,8 +104,8 @@ object PathTransformer { * not be processed (successfully), they will not occur in the tree. */ def pathToStringTree[State <: ComputationState](path: Path)(implicit - state: State, - iHandler: InterpretationHandler[State] + state: State, + ps: PropertyStore ): StringTree = { val tree = path.elements.size match { case 1 => 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 8212e92d9d..fa441a066e 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,13 +4,10 @@ package tac package fpcf package analyses -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.br.Method /** - * @author Patrick Mell + * @author Maximilian Rüsch */ package object string_analysis { @@ -29,20 +26,8 @@ package object string_analysis { type SContext = (SEntity, Method) /** - * 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[IPResult]]] - - /** - * This type serves as a lookup mechanism to find out which functions parameters map to which - * argument position. That is, the element of type [[SContext]] 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. + * The entity used for requesting string constancy information for specific def sites of an entity. The def site + * should be given as a [[org.opalj.br.PC]]. */ - type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[SContext, (Int, Int, Int)]] + case class DefSiteEntity(pc: Int, state: ComputationState) } From ea74cbb29c08ee967e05144f9cca5cd78b008c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 8 Mar 2024 13:45:33 +0100 Subject: [PATCH 381/583] Group all simple value const expr interpreters --- .../DoubleValueInterpreter.scala | 37 --------------- .../FloatValueInterpreter.scala | 37 --------------- .../IntegerValueInterpreter.scala | 38 ---------------- .../SimpleValueConstExprInterpreter.scala | 45 +++++++++++++++++++ .../StringConstInterpreter.scala | 38 ---------------- .../L0InterpretationHandler.scala | 12 ++--- .../L1InterpretationHandler.scala | 10 +---- 7 files changed, 50 insertions(+), 167 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala 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 deleted file mode 100644 index 546073dd5d..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -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.fpcf.ProperPropertyComputationResult - -/** - * @author Maximilian Rüsch - */ -case class DoubleValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { - - override type T = DoubleConst - - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = - computeFinalResult( - defSite, - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ) - ) -} - -object DoubleValueInterpreter { - - def interpret[State <: ComputationState](instr: DoubleConst, defSite: Int)(implicit - state: State - ): ProperPropertyComputationResult = DoubleValueInterpreter[State]().interpret(instr, defSite) -} 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 deleted file mode 100644 index e9a2ff8c77..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -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.fpcf.ProperPropertyComputationResult - -/** - * @author Maximilian Rüsch - */ -case class FloatValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { - - override type T = FloatConst - - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = - computeFinalResult( - defSite, - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ) - ) -} - -object FloatValueInterpreter { - - def interpret[State <: ComputationState](instr: FloatConst, defSite: Int)(implicit - state: State - ): ProperPropertyComputationResult = FloatValueInterpreter[State]().interpret(instr, defSite) -} 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 deleted file mode 100644 index 51569f4127..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org -package opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -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.fpcf.ProperPropertyComputationResult - -/** - * @author Maximilian Rüsch - */ -case class IntegerValueInterpreter[State <: ComputationState]() extends StringInterpreter[State] { - - override type T = IntConst - - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = - computeFinalResult( - defSite, - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value.toString - ) - ) -} - -object IntegerValueInterpreter { - - def interpret[State <: ComputationState](instr: IntConst, defSite: Int)(implicit - state: State - ): ProperPropertyComputationResult = IntegerValueInterpreter[State]().interpret(instr, defSite) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala new file mode 100644 index 0000000000..1f95a40737 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package interpretation + +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.fpcf.ProperPropertyComputationResult + +/** + * @author Maximilian Rüsch + */ +case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends StringInterpreter[State] { + + override type T = SimpleValueConst + + def interpret(expr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + val sci = expr match { + case ic: IntConst => + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, ic.value.toString) + case fc: FloatConst => + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, fc.value.toString) + case dc: DoubleConst => + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, dc.value.toString) + case lc: LongConst => + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, lc.value.toString) + case sc: StringConst => + StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, sc.value) + case _ => + StringConstancyInformation.getNeutralElement + } + computeFinalResult(defSite, sci) + } +} + +object SimpleValueConstExprInterpreter { + + def interpret[State <: ComputationState](instr: SimpleValueConst, defSite: Int)(implicit + state: State + ): ProperPropertyComputationResult = SimpleValueConstExprInterpreter[State]().interpret(instr, defSite) +} 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 deleted file mode 100644 index 9b0d5848aa..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package interpretation - -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.fpcf.ProperPropertyComputationResult -import org.opalj.tac.StringConst - -/** - * @author Maximilian Rüsch - */ -case class StringConstInterpreter[State <: ComputationState]() extends StringInterpreter[State] { - - override type T = StringConst - - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = - computeFinalResult( - defSite, - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND, - instr.value - ) - ) -} - -object StringConstInterpreter { - - def interpret[State <: ComputationState](instr: StringConst, defSite: Int)(implicit - state: State - ): ProperPropertyComputationResult = StringConstInterpreter[State]().interpret(instr, defSite) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 7fa759c579..2859ab21cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -12,11 +12,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.SimpleValueConstExprInterpreter /** * @inheritdoc @@ -31,11 +28,8 @@ class L0InterpretationHandler[State <: L0ComputationState]()( override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { state.tac.stmts(defSite) match { - case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 3a5c27dae3..db5ff893f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -19,11 +19,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.DoubleValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.FloatValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntegerValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.StringConstInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NewArrayInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualFunctionCallInterpreter @@ -47,10 +44,7 @@ class L1InterpretationHandler[State <: L1ComputationState]( override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { state.tac.stmts(defSite) match { - case Assignment(_, _, expr: StringConst) => StringConstInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: IntConst) => IntegerValueInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: FloatConst) => FloatValueInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: DoubleConst) => DoubleValueInterpreter.interpret(expr, defSite) // TODO what about long const + case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) From dc467a57287ca5167a3c2f4822973a17f2fbe575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 8 Mar 2024 13:56:45 +0100 Subject: [PATCH 382/583] Remove debug statements from property store --- .../main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala | 5 ----- 1 file changed, 5 deletions(-) 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 index 9b132c570b..91c7822468 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -4,7 +4,6 @@ package fpcf package par import scala.annotation.switch -import scala.annotation.unused import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.LinkedBlockingQueue @@ -363,10 +362,6 @@ class PKECPropertyStore( val SomeEPS(e, pk) = interimEP var isFresh = false - if (interimEP.pk.id == 0 && ps(pk.id).containsKey(e)) { - @unused val a = "" - } - val ePKState = ps(pk.id).computeIfAbsent(e, { _ => isFresh = true; EPKState(interimEP, c, dependees) }) if (isFresh) { From 40b2dfd5b16d9249ab3c1ef858934b6df685f20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 8 Mar 2024 14:28:24 +0100 Subject: [PATCH 383/583] Use PCs for interpretation instead of def sites --- .../string_analysis/StringInterpreter.scala | 19 ++--- .../BinaryExprInterpreter.scala | 8 +-- .../InterpretationHandler.scala | 35 +++------- .../SimpleValueConstExprInterpreter.scala | 8 +-- .../L0ArrayAccessInterpreter.scala | 18 +++-- .../L0FunctionCallInterpreter.scala | 6 +- .../L0InterpretationHandler.scala | 39 ++++++----- .../L0NewArrayInterpreter.scala | 19 +++-- .../L0NonVirtualFunctionCallInterpreter.scala | 10 ++- .../L0NonVirtualMethodCallInterpreter.scala | 49 ++++--------- .../L0StaticFunctionCallInterpreter.scala | 24 +++---- .../L0VirtualFunctionCallInterpreter.scala | 69 +++++++++---------- .../L0VirtualMethodCallInterpreter.scala | 6 +- .../L1FieldReadInterpreter.scala | 18 ++--- .../L1InterpretationHandler.scala | 41 ++++++----- .../L1VirtualFunctionCallInterpreter.scala | 12 ++-- 16 files changed, 159 insertions(+), 222 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index b0455cec42..3dd53eb717 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -27,17 +27,14 @@ trait StringInterpreter[State <: ComputationState] { /** * @param instr The instruction that is to be interpreted. - * @param defSite The definition site that corresponds to the given instruction. - * @return A [[ProperPropertyComputationResult]] for the given def site containing the interpretation of the given + * @param pc The pc that corresponds to the given instruction. + * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given * instruction. */ - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult + def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult - def computeFinalResult(finalEP: FinalEP[DefSiteEntity, StringConstancyProperty]): Result = - StringInterpreter.computeFinalResult(finalEP) - - def computeFinalResult(defSite: Int, sci: StringConstancyInformation)(implicit state: State): Result = - StringInterpreter.computeFinalResult(defSite, sci) + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: State): Result = + StringInterpreter.computeFinalResult(pc, sci) // IMPROVE remove this since awaiting all final is not really feasible // replace with intermediate lattice result approach @@ -74,12 +71,10 @@ trait StringInterpreter[State <: ComputationState] { object StringInterpreter { - def computeFinalResult(finalEP: FinalEP[DefSiteEntity, StringConstancyProperty]): Result = Result(finalEP) - - def computeFinalResult[State <: ComputationState](defSite: Int, sci: StringConstancyInformation)(implicit + def computeFinalResult[State <: ComputationState](pc: Int, sci: StringConstancyInformation)(implicit state: State ): Result = - computeFinalResult(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty(sci))) + Result(FinalEP(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty(sci))) } trait ParameterEvaluatingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { 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 3841b77c21..589c03c477 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 @@ -26,19 +26,19 @@ case class BinaryExprInterpreter[State <: ComputationState]() extends StringInte * * For all other expressions, a [[StringConstancyInformation.getNeutralElement]] will be returned. */ - def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val sci = instr.cTpe match { case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat case _ => StringConstancyInformation.getNeutralElement } - computeFinalResult(defSite, sci) + computeFinalResult(pc, sci) } } object BinaryExprInterpreter { - def interpret[State <: ComputationState](instr: BinaryExpr[V], defSite: Int)(implicit + def interpret[State <: ComputationState](instr: BinaryExpr[V], pc: Int)(implicit state: State - ): ProperPropertyComputationResult = BinaryExprInterpreter[State]().interpret(instr, defSite) + ): ProperPropertyComputationResult = BinaryExprInterpreter[State]().interpret(instr, pc) } 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 15f062569a..d9675c0292 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 @@ -12,13 +12,10 @@ import scala.collection.mutable.ListBuffer import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset 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.fpcf.FinalEP import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result /** * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site @@ -32,37 +29,21 @@ import org.opalj.fpcf.Result abstract class InterpretationHandler[State <: ComputationState] { def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = - processDefSite(valueOriginOfPC(entity.pc, entity.state.tac.pcToIndex).get)(entity.state.asInstanceOf[State]) + processDefSitePC(entity.pc)(entity.state.asInstanceOf[State]) - /** - * 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) can be processed by one of the subclasses of [[StringInterpreter]] (in case (3) is violated, - * an [[IllegalArgumentException]] will be thrown). - * @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`. - */ - private def processDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - if (defSite <= FormalParametersOriginOffset) { - if (defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset) { - return Result(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty.lb)) + private def processDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + if (pc <= FormalParametersOriginOffset) { + if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { + return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) } else { - val sci = StringConstancyInformation.getElementForParameterPC(pcOfDefSite(defSite)(state.tac.stmts)) - return Result(FinalEP(InterpretationHandler.getEntityFromDefSite(defSite), StringConstancyProperty(sci))) + return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getElementForParameterPC(pc)) } } - processNewDefSite(defSite) + processNewDefSitePC(pc) } - protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult + protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult } object InterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index 1f95a40737..71149da1fb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -18,7 +18,7 @@ case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends override type T = SimpleValueConst - def interpret(expr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + def interpret(expr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val sci = expr match { case ic: IntConst => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, ic.value.toString) @@ -33,13 +33,13 @@ case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends case _ => StringConstancyInformation.getNeutralElement } - computeFinalResult(defSite, sci) + computeFinalResult(pc, sci) } } object SimpleValueConstExprInterpreter { - def interpret[State <: ComputationState](instr: SimpleValueConst, defSite: Int)(implicit + def interpret[State <: ComputationState](instr: SimpleValueConst, pc: Int)(implicit state: State - ): ProperPropertyComputationResult = SimpleValueConstExprInterpreter[State]().interpret(instr, defSite) + ): ProperPropertyComputationResult = SimpleValueConstExprInterpreter[State]().interpret(instr, pc) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index b5bdc929eb..35b8afb900 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -26,30 +26,28 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertySto override type T = ArrayLoad[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - implicit val stmts: Array[Stmt[V]] = state.tac.stmts - - val defSitePCs = getStoreAndLoadDefSitePCs(instr) + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + val defSitePCs = getStoreAndLoadDefSitePCs(instr)(state.tac.stmts) val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSite(defSite), + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( - EPSDepender(instr, pcOfDefSite(defSite), state, results), - finalResult(pcOfDefSite(defSite)) + EPSDepender(instr, pc, state, results), + finalResult(pc) ) ) } else { - finalResult(defSite)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } } - private def finalResult(defSite: Int)(results: Iterable[SomeFinalEP])(implicit + private def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit state: State ): ProperPropertyComputationResult = { var resultSci = StringConstancyInformation.reduceMultiple(results.map { @@ -59,7 +57,7 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertySto resultSci = StringConstancyInformation.lb } - computeFinalResult(defSite, resultSci) + computeFinalResult(pc, resultSci) } private def getStoreAndLoadDefSitePCs(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala index 350c2a38c0..9726284762 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -12,7 +12,6 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore @@ -152,10 +151,7 @@ trait L0FunctionCallInterpreter[State <: L0ComputationState] } } - computeFinalResult(FinalEP( - InterpretationHandler.getEntityFromDefSitePC(callState.defSitePC), - StringConstancyProperty(StringConstancyInformation.reduceMultiple(methodScis)) - )) + computeFinalResult(callState.defSitePC, StringConstancyInformation.reduceMultiple(methodScis)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 2859ab21cd..d7a64e5596 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -26,40 +26,45 @@ class L0InterpretationHandler[State <: L0ComputationState]()( ps: PropertyStore ) extends InterpretationHandler[State] { - override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - state.tac.stmts(defSite) match { - case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) + override protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); + if (defSiteOpt.isEmpty) { + throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") + } + + state.tac.stmts(defSiteOpt.get) match { + case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) - case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) - case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) + case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) // Currently unsupported case Assignment(_, _, _: GetField[V]) => - StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.lb) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(ps).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(ps).interpret(expr, pc) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(ps).interpret(expr, defSite) + L0VirtualFunctionCallInterpreter(ps).interpret(expr, pc) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) + L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) + L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, pc) case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, pc) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, defSite) - case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, defSite) + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, pc) + case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) case _ => - StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index af50317da0..87eb99aa40 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -28,16 +28,15 @@ class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) override type T = NewArray[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { if (instr.counts.length != 1) { // Only supports 1-D arrays - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } // Get all sites that define array values and process them - val arrValuesDefSites = - state.tac.stmts(defSite).asAssignment.targetVar.asVar.usedBy.toArray.toList.sorted + val defSite = valueOriginOfPC(pc, state.tac.pcToIndex).get; + val arrValuesDefSites = state.tac.stmts(defSite).asAssignment.targetVar.asVar.usedBy.toArray.toList.sorted val allResults = arrValuesDefSites.flatMap { ds => if (ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[V]]) { state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { ds => @@ -52,16 +51,16 @@ class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) if (allResults.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSite(defSite), + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, allResults.filter(_.isRefinable).toSet, awaitAllFinalContinuation( - EPSDepender(instr, defSitePC, state, allResults), - finalResult(defSitePC) + EPSDepender(instr, pc, state, allResults), + finalResult(pc) ) ) } else { - finalResult(defSitePC)(allResults.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(allResults.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } } @@ -74,6 +73,6 @@ class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) StringConstancyInformation.reduceMultiple(resultsScis) } - computeFinalResult(valueOriginOfPC(pc, state.tac.pcToIndex).get, sci) + computeFinalResult(pc, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 0cdcd0e9f1..18df3219c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -26,17 +26,15 @@ case class L0NonVirtualFunctionCallInterpreter[State <: L0ComputationState]()( override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } val m = calleeMethod.value - val callState = FunctionCallState(defSite, Seq(m), Map((m, ps(m, TACAI.key)))) - - val params = evaluateParameters(getParametersForPC(pcOfDefSite(defSite)(state.tac.stmts))) - callState.setParamDependees(params) + val callState = FunctionCallState(pc, Seq(m), Map((m, ps(m, TACAI.key)))) + callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) interpretArbitraryCallToMethods(state, callState) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 94ecd5ff7e..509aaaba90 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -18,8 +18,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Responsible for processing [[NonVirtualMethodCall]]s without a call graph. - * * @author Maximilian Rüsch */ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: PropertyStore) @@ -27,37 +25,18 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr 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, a [[NoIPResult]] will be returned. - */ - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { - case "" => interpretInit(instr) - case _ => computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + case "" => interpretInit(instr, pc) + case _ => computeFinalResult(pc, StringConstancyInformation.getNeutralElement) } } - /** - * Processes an `<init>` method call. If it has no parameters, [[NoIPResult]] 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: T)(implicit state: State): ProperPropertyComputationResult = { - val entity = InterpretationHandler.getEntityFromDefSitePC(init.pc) - + private def interpretInit(init: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { init.params.size match { - case 0 => computeFinalResult(FinalEP(entity, StringConstancyProperty.getNeutralElement)) + case 0 => computeFinalResult(pc, StringConstancyInformation.getNeutralElement) case _ => + // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val results = init.params.head.asVar.definedBy.toList.map { ds => ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) } @@ -65,12 +44,12 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr finalResult(init.pc)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } else { InterimResult.forLB( - entity, + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, results.toSet, awaitAllFinalContinuation( - EPSDepender(init, init.pc, state, results), - finalResult(init.pc) + EPSDepender(init, pc, state, results), + finalResult(pc) ) ) } @@ -80,10 +59,10 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr private def finalResult(pc: Int)(results: Iterable[SomeEPS])(implicit state: State ): Result = - computeFinalResult(FinalEP( - InterpretationHandler.getEntityFromDefSitePC(pc), - StringConstancyProperty(StringConstancyInformation.reduceMultiple(results.map { + computeFinalResult( + pc, + StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].sci - })) - )) + }) + ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 2f3355e610..ff7b63479e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -22,8 +22,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.properties.TACAI /** - * Processes [[StaticFunctionCall]]s without a call graph. - * * @author Maximilian Rüsch */ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]()( @@ -36,10 +34,10 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]()( override type T = StaticFunctionCall[V] - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { - case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, defSite) - case _ => interpretArbitraryCall(instr, defSite) + case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, pc) + case _ => interpretArbitraryCall(instr, pc) } } } @@ -52,19 +50,17 @@ private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter[State <: override type T = StaticFunctionCall[V] - def interpretArbitraryCall(instr: T, defSite: Int)(implicit + def interpretArbitraryCall(instr: T, pc: Int)(implicit state: State ): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } val m = calleeMethod.value - val callState = FunctionCallState(pcOfDefSite(defSite)(state.tac.stmts), Seq(m), Map((m, ps(m, TACAI.key)))) - - val params = evaluateParameters(getParametersForPC(pcOfDefSite(defSite)(state.tac.stmts))) - callState.setParamDependees(params) + val callState = FunctionCallState(pc, Seq(m), Map((m, ps(m, TACAI.key)))) + callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) interpretArbitraryCallToMethods(state, callState) } @@ -77,7 +73,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L val ps: PropertyStore - def processStringValueOf(call: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + def processStringValueOf(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { def finalResult(results: Iterable[SomeFinalEP]): Result = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } @@ -92,7 +88,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L } else { scis } - computeFinalResult(defSite, StringConstancyInformation.reduceMultiple(finalScis)) + computeFinalResult(pc, StringConstancyInformation.reduceMultiple(finalScis)) } val results = call.params.head.asVar.definedBy.toList.sorted.map { ds => @@ -100,7 +96,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L } if (results.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSite(defSite), + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 0bb4d0474c..769ca49db1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -68,20 +68,20 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { - case "append" => interpretAppendCall(instr, defSite) - case "toString" => interpretToStringCall(instr, defSite) - case "replace" => interpretReplaceCall(defSite) + case "append" => interpretAppendCall(instr, pc) + case "toString" => interpretToStringCall(instr, pc) + case "replace" => interpretReplaceCall(pc) case "substring" if instr.descriptor.returnType == ObjectType.String => - interpretSubstringCall(instr, defSite) + interpretSubstringCall(instr, pc) case _ => instr.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => - interpretArbitraryCall(instr, defSite) + interpretArbitraryCall(instr, pc) case FloatType | DoubleType => computeFinalResult( - defSite, + pc, StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, @@ -89,7 +89,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( ) ) case _ => - computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + computeFinalResult(pc, StringConstancyInformation.getNeutralElement) } } } @@ -98,15 +98,15 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( * Processes 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: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + private def interpretToStringCall(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { eps match { case FinalP(sciP: StringConstancyProperty) => - computeFinalResult(defSite, sciP.sci) + computeFinalResult(pc, sciP.sci) case iep: InterimEP[_, _] if eps.pk == StringConstancyProperty.key => InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(call.pc), + InterpretationHandler.getEntityFromDefSitePC(pc), iep.lb.asInstanceOf[StringConstancyProperty], Set(eps), computeResult @@ -114,7 +114,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( case _ if eps.pk == StringConstancyProperty.key => InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(call.pc), + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, Set(eps), computeResult @@ -133,17 +133,15 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(defSite: Int)(implicit state: State): ProperPropertyComputationResult = - computeFinalResult(defSite, InterpretationHandler.getStringConstancyInformationForReplace) + private def interpretReplaceCall(pc: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult(pc, InterpretationHandler.getStringConstancyInformationForReplace) } private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter[State <: L0ComputationState] extends L0StringInterpreter[State] { - protected def interpretArbitraryCall(call: T, defSite: Int)(implicit - state: State - ): ProperPropertyComputationResult = - computeFinalResult(defSite, StringConstancyInformation.lb) + protected def interpretArbitraryCall(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = + computeFinalResult(pc, StringConstancyInformation.lb) } /** @@ -185,7 +183,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta receiverDependees.filter(_.isRefinable) ++ valueDependees.filter(_.isRefinable) } - def interpretAppendCall(appendCall: T, defSite: Int)(implicit + def interpretAppendCall(appendCall: T, pc: Int)(implicit state: State ): ProperPropertyComputationResult = { // Get receiver results @@ -203,8 +201,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta } ps(InterpretationHandler.getEntityFromDefSite(usedDS), StringConstancyProperty.key) } - implicit val appendState: AppendCallState = - AppendCallState(appendCall, param, pcOfDefSite(defSite)(state.tac.stmts), receiverResults, valueResults) + implicit val appendState: AppendCallState = AppendCallState(appendCall, param, pc, receiverResults, valueResults) tryComputeFinalAppendCallResult } @@ -241,14 +238,14 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta appendState.valueDependees.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]] ) - computeFinalResult(FinalEP( - InterpretationHandler.getEntityFromDefSitePC(appendState.defSitePC), - StringConstancyProperty(StringConstancyInformation( + computeFinalResult( + appendState.defSitePC, + StringConstancyInformation( StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, valueSci.constancyLevel), StringConstancyType.APPEND, receiverSci.possibleStrings + valueSci.possibleStrings - )) - )) + ) + ) } } @@ -297,7 +294,7 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation val ps: PropertyStore - def interpretSubstringCall(substringCall: T, defSite: Int)(implicit + def interpretSubstringCall(substringCall: T, pc: Int)(implicit state: State ): ProperPropertyComputationResult = { val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => @@ -306,20 +303,20 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation if (receiverResults.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSite(defSite), + InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.lb, receiverResults.toSet, awaitAllFinalContinuation( EPSDepender(substringCall, substringCall.pc, state, receiverResults), - computeFinalSubstringCallResult(substringCall, defSite) + computeFinalSubstringCallResult(substringCall, pc) ) ) } else { - computeFinalSubstringCallResult(substringCall, defSite)(receiverResults.asInstanceOf[Iterable[SomeFinalEP]]) + computeFinalSubstringCallResult(substringCall, pc)(receiverResults.asInstanceOf[Iterable[SomeFinalEP]]) } } - private def computeFinalSubstringCallResult(substringCall: T, defSite: Int)( + private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( results: Iterable[SomeFinalEP] )(implicit state: State): Result = { val receiverSci = StringConstancyInformation.reduceMultiple(results.map { @@ -327,7 +324,7 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation }) if (receiverSci.isTheNeutralElement || receiverSci.isComplex) { // We cannot yet interpret substrings of mixed values - computeFinalResult(defSite, StringConstancyInformation.lb) + computeFinalResult(pc, StringConstancyInformation.lb) } else { val parameterCount = substringCall.params.size parameterCount match { @@ -335,7 +332,7 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation substringCall.params.head.asVar.value match { case intValue: TheIntegerValue => computeFinalResult( - defSite, + pc, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.REPLACE, @@ -343,14 +340,14 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation ) ) case _ => - computeFinalResult(defSite, StringConstancyInformation.lb) + computeFinalResult(pc, StringConstancyInformation.lb) } case 2 => (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => computeFinalResult( - defSite, + pc, StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, @@ -358,7 +355,7 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation ) ) case _ => - computeFinalResult(defSite, StringConstancyInformation.lb) + computeFinalResult(pc, StringConstancyInformation.lb) } case _ => throw new IllegalStateException( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index ba3295152a..606876f7e3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -13,8 +13,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.ProperPropertyComputationResult /** - * Responsible for processing [[VirtualMethodCall]]s without a call graph. - * * @author Maximilian Rüsch */ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends L0StringInterpreter[State] { @@ -34,12 +32,12 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends * * For all other calls, a [[StringConstancyInformation.getNeutralElement]] will be returned. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) case _ => StringConstancyInformation.getNeutralElement } - computeFinalResult(defSite, sci) + computeFinalResult(pc, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 4412fb6a5b..69e0da0834 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -15,7 +15,6 @@ 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.fpcf.EOptionP -import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore @@ -95,19 +94,17 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - + override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } if (writeAccesses.isEmpty) { @@ -117,10 +114,10 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( possibleStrings = s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" ) - return computeFinalResult(defSite, sci) + return computeFinalResult(pc, sci) } - implicit val accessState: FieldReadState = FieldReadState(defSitePC) + implicit val accessState: FieldReadState = FieldReadState(pc) writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod @@ -165,10 +162,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( scis = scis :+ StringConstancyInformation.lb } - computeFinalResult(FinalEP( - InterpretationHandler.getEntityFromDefSitePC(accessState.defSitePC), - StringConstancyProperty(StringConstancyInformation.reduceMultiple(scis)) - )) + computeFinalResult(accessState.defSitePC, StringConstancyInformation.reduceMultiple(scis)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index db5ff893f4..a358b27bd7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -42,51 +42,56 @@ class L1InterpretationHandler[State <: L1ComputationState]( val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processNewDefSite(defSite: Int)(implicit state: State): ProperPropertyComputationResult = { - state.tac.stmts(defSite) match { - case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, defSite) + override protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); + if (defSiteOpt.isEmpty) { + throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") + } + + state.tac.stmts(defSiteOpt.get) match { + case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) - case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, defSite) - case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, defSite) + case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) + case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) case Assignment(_, _, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( expr, - defSite + pc ) case ExprStmt(_, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( expr, - defSite + pc ) case Assignment(_, _, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpret(expr, defSite) + new L1VirtualFunctionCallInterpreter().interpret(expr, pc) case ExprStmt(_, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpret(expr, defSite) + new L1VirtualFunctionCallInterpreter().interpret(expr, pc) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) + L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, defSite) + L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, pc) case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, defSite) + L0StaticFunctionCallInterpreter().interpret(expr, pc) // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, defSite) + case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter().interpret(vmc, defSite) + L0VirtualMethodCallInterpreter().interpret(vmc, pc) case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, defSite) + L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) case _ => - StringInterpreter.computeFinalResult(defSite, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index e0162a53fe..96acff0c63 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -30,22 +30,18 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( override type T = VirtualFunctionCall[V] - override protected def interpretArbitraryCall(instr: T, defSite: Int)( + override protected def interpretArbitraryCall(instr: T, pc: Int)( implicit state: State ): ProperPropertyComputationResult = { - val defSitePC = pcOfDefSite(defSite)(state.tac.stmts) - // IMPROVE add some uncertainty element if methods with unknown body exist val (methods, _) = getMethodsForPC(instr.pc) if (methods.isEmpty) { - return computeFinalResult(defSite, StringConstancyInformation.lb) + return computeFinalResult(pc, StringConstancyInformation.lb) } - val params = evaluateParameters(getParametersForPC(defSitePC)) val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - - val callState = FunctionCallState(defSitePC, tacDependees.keys.toSeq, tacDependees) - callState.setParamDependees(params) + val callState = FunctionCallState(pc, tacDependees.keys.toSeq, tacDependees) + callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) interpretArbitraryCallToMethods(state, callState) } From 1d2a997ffdc2fecd24f5f76b5e890137db732d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 8 Mar 2024 14:57:43 +0100 Subject: [PATCH 384/583] Fix complex sci detection for parameters --- .../string_definition/StringConstancyInformation.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 92de3a4d81..7348382dc0 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 @@ -37,7 +37,8 @@ case class StringConstancyInformation( possibleStrings.contains("|") || possibleStrings.contains("?") || possibleStrings.contains("(") || - possibleStrings.contains(")") + possibleStrings.contains(")") || + possibleStrings.contains(StringConstancyInformation.ParameterPrefix) def fillInParameters(paramScis: Seq[StringConstancyInformation]): StringConstancyInformation = { if (possibleStrings.contains( From f1903f8f1a3db566a38c61658336139a398d1344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 13 Mar 2024 22:58:24 +0100 Subject: [PATCH 385/583] Rebuild string constancy information with constancy trees --- .../string_analysis/l0/L0TestMethods.java | 29 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 2 +- .../StringAnalysisMatcher.scala | 2 +- .../properties/StringConstancyProperty.scala | 28 +- .../StringConstancyInformation.scala | 167 +------- .../StringConstancyLevel.scala | 1 - .../string_definition/StringTree.scala | 374 ------------------ .../string_definition/StringTreeNode.scala | 227 +++++++++++ .../string_analysis/StringAnalysis.scala | 51 +-- .../string_analysis/StringInterpreter.scala | 4 +- .../BinaryExprInterpreter.scala | 8 +- .../InterpretationHandler.scala | 38 +- .../SimpleValueConstExprInterpreter.scala | 26 +- .../L0ArrayAccessInterpreter.scala | 6 +- .../L0FunctionCallInterpreter.scala | 11 +- .../L0InterpretationHandler.scala | 4 +- .../L0NewArrayInterpreter.scala | 4 +- .../L0NonVirtualMethodCallInterpreter.scala | 8 +- .../L0StaticFunctionCallInterpreter.scala | 16 +- .../L0VirtualFunctionCallInterpreter.scala | 52 ++- .../L0VirtualMethodCallInterpreter.scala | 7 +- .../string_analysis/l1/L1StringAnalysis.scala | 19 +- .../L1FieldReadInterpreter.scala | 18 +- .../L1InterpretationHandler.scala | 4 +- .../preprocessing/AbstractPathFinder.scala | 95 +++-- .../string_analysis/preprocessing/Path.scala | 39 +- .../preprocessing/PathTransformer.scala | 25 +- 27 files changed, 463 insertions(+), 802 deletions(-) delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 6dac43695e..54eeac4dee 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -441,8 +441,7 @@ public void switchNestedWithNestedDefault(int value, int value2) { } @StringDefinitionsCollection( - value = "if-else control structure which append to a string builder with an int expr " - + "and an int", + value = "if-else control structure which append to a string builder with an int expr and an int", stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)" @@ -958,6 +957,30 @@ public void breakContinueExamples(int value) { analyzeString(sb3.toString()); } + + + @StringDefinitionsCollection( + value = "loops that use breaks and continues (or both)", + stringDefinitions = { + @StringDefinitions( + // The bytecode produces an "if" within an "if" inside the first loop, => two conditions + expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" + ) + }) + public void breakContinueExamples2(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()); + } + @StringDefinitionsCollection( value = "an example where in the condition of an 'if', a string is appended to a " + "StringBuilder", @@ -1080,7 +1103,7 @@ public void nonFinalFieldRead() { @StringDefinitionsCollection( value = "an example that reads a public final static field; for these, the string " - + "information are available (at lease on modern compilers)", + + "information are available (at least on modern compilers)", stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "Field Value:mine" 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 3ea8e79f79..6e345bbf84 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 @@ -160,7 +160,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities - // .filter(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) + // .filter(entity => entity._2.name.startsWith("whileWithBreak")) // .filter(entity => entity._2.name.startsWith("tryCatchFinally")) // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) .filterNot(entity => entity._2.name.startsWith("switchNested")) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index e8018a8fb4..9f4d9dde5e 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -67,7 +67,7 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { case prop: StringConstancyProperty => val sci = prop.stringConstancyInformation actLevel = sci.constancyLevel.toString.toLowerCase - actString = sci.possibleStrings + actString = sci.tree.toRegex case _ => } 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 82560f31c8..86be4aac12 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 @@ -5,8 +5,6 @@ package fpcf package properties 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.fpcf.Entity import org.opalj.fpcf.FallbackReason import org.opalj.fpcf.Property @@ -28,7 +26,7 @@ class StringConstancyProperty( override def toString: String = { val level = stringConstancyInformation.constancyLevel.toString.toLowerCase - s"Level: $level, Possible Strings: ${stringConstancyInformation.possibleStrings}" + s"Level: $level, Possible Strings: ${stringConstancyInformation.toRegex}" } /** @@ -62,30 +60,16 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta ) } - def apply( - stringConstancyInformation: StringConstancyInformation - ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) + def apply(stringConstancyInformation: StringConstancyInformation): StringConstancyProperty = + new StringConstancyProperty(stringConstancyInformation) /** - * @return Returns the / a neutral [[StringConstancyProperty]] element, i.e., an element for - * which [[StringConstancyProperty.isTheNeutralElement]] is `true`. + * @return Returns the lower bound from a lattice-point of view. */ - def getNeutralElement: StringConstancyProperty = - StringConstancyProperty(StringConstancyInformation.getNeutralElement) + def lb: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation.lb) /** * @return Returns the upper bound from a lattice-point of view. */ - def ub: StringConstancyProperty = - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.CONSTANT, - StringConstancyType.APPEND - )) - - /** - * @return Returns the lower bound from a lattice-point of view. - */ - def lb: StringConstancyProperty = - StringConstancyProperty(StringConstancyInformation.lb) - + def ub: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation.ub) } 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 7348382dc0..9532b6c326 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 @@ -6,68 +6,28 @@ package properties package string_definition /** - * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this - * parameter can be omitted. - * @author Patrick Mell + * @author Maximilian Rüsch */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, - constancyType: StringConstancyType.Value = StringConstancyType.APPEND, - possibleStrings: String = "" + constancyType: StringConstancyType.Value = StringConstancyType.APPEND, + tree: StringTreeNode = StringTreeNeutralElement ) { - /** - * 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 == "" - - /** - * Checks whether the instance is a complex element in the sense that it is either non-constant or contains some - * characters that make finding substrings harder. - */ - def isComplex: Boolean = - constancyLevel != StringConstancyLevel.CONSTANT || - possibleStrings.contains("|") || - possibleStrings.contains("?") || - possibleStrings.contains("(") || - possibleStrings.contains(")") || - possibleStrings.contains(StringConstancyInformation.ParameterPrefix) + tree.isNeutralElement - def fillInParameters(paramScis: Seq[StringConstancyInformation]): StringConstancyInformation = { - if (possibleStrings.contains( - StringConstancyInformation.ParameterPrefix + paramScis.size + StringConstancyInformation.ParameterSuffix - ) - ) { - throw new IllegalStateException("Insufficient parameters given!") - } - - var strings = possibleStrings - paramScis.zipWithIndex.foreach { - case (sci, index) => - strings = strings.replace( - StringConstancyInformation.ParameterPrefix + index + StringConstancyInformation.ParameterSuffix, - sci.possibleStrings - ) - } - - this.copy(possibleStrings = strings) - } + def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel - def fillInParametersWithLB: StringConstancyInformation = { - val strings = possibleStrings.replaceAll("""\$_\[\d+]""", ".*") + def toRegex: String = tree.toRegex - // IMPROVE enable backwards parse of string constancy information - val level = if (strings == ".*") StringConstancyLevel.DYNAMIC - else StringConstancyLevel.PARTIALLY_CONSTANT + def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { + val newTree = tree.replaceParameters(parameters.map { + case (index, sci) => (index, sci.tree) + }) - this.copy(possibleStrings = strings, constancyLevel = level) + StringConstancyInformation(constancyType, newTree) } } @@ -76,46 +36,6 @@ case class StringConstancyInformation( */ 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 = ".*" - - /** - * The stringified version of a (dynamic) integer value. - */ - val IntValue: String = "^-?\\d+$" - - /** - * The stringified version of a (dynamic) float value. - */ - val FloatValue: String = "^-?\\d*\\.{0,1}\\d+$" - - /** - * A value to be used when the number of an element, that is repeated, is unknown. - */ - val InfiniteRepetitionSymbol: String = "*" - - /** - * A value to be used to indicate that a string expression might be null. - */ - val NullStringValue: String = "^null$" - - /** - * The prefix given to placeholders representing function parameters. - * - * @see [[getElementForParameterPC]] - */ - val ParameterPrefix: String = "$_[" - - /** - * The suffix given to placeholders representing function parameters. - * - * @see [[getElementForParameterPC]] - */ - val ParameterSuffix: 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 @@ -127,64 +47,22 @@ 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: Iterable[StringConstancyInformation]): StringConstancyInformation = { + def reduceMultiple(scis: Seq[StringConstancyInformation]): StringConstancyInformation = { val relScis = scis.filter(!_.isTheNeutralElement) 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 - case 0 => StringConstancyInformation.getNeutralElement + case 0 => neutralElement case 1 => relScis.head - case _ => // Deduplicate and reduce - var seenStrings = Set.empty[String] - val reduced = relScis.flatMap { sci => - if (seenStrings.contains(sci.possibleStrings)) { - None - } else { - seenStrings += sci.possibleStrings - Some(sci) - } - } 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})" - ) + case _ => StringConstancyInformation(StringConstancyType.APPEND, StringTreeOr(relScis.map(_.tree))) } } - /** - * @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`. - */ - def getNeutralElement: StringConstancyInformation = - StringConstancyInformation(StringConstancyLevel.CONSTANT) + def lb: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicString) + def ub: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNeutralElement) + def dynamicInt: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicInt) + def dynamicFloat: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicFloat) - /** - * @return Returns a [[StringConstancyInformation]] element to indicate a `null` value. - */ - def getNullElement: StringConstancyInformation = - StringConstancyInformation(StringConstancyLevel.CONSTANT, possibleStrings = NullStringValue) + def neutralElement: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNeutralElement) + def nullElement: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNull) def getElementForParameterPC(paramPC: Int): StringConstancyInformation = { if (paramPC >= -1) { @@ -192,9 +70,6 @@ object StringConstancyInformation { } // Parameters start at PC -2 downwards val paramPosition = Math.abs(paramPC + 2) - StringConstancyInformation( - StringConstancyLevel.CONSTANT, - possibleStrings = ParameterPrefix + paramPosition + ParameterSuffix - ) + StringConstancyInformation(tree = StringTreeParameter(paramPosition)) } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala index 39d22d1361..3683de61e5 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala @@ -79,5 +79,4 @@ object StringConstancyLevel extends Enumeration { level1 } } - } 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 deleted file mode 100644 index d887ee10dc..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala +++ /dev/null @@ -1,374 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package fpcf -package properties -package string_definition - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.InfiniteRepetitionSymbol - -/** - * Super type for modeling nodes and leafs of [[org.opalj.br.fpcf.properties.string_definition.StringTree]]s. - * - * @author Patrick Mell - */ -sealed abstract class StringTree(val children: ListBuffer[StringTree]) { // question do it even make sense to have mutable here? - - /** - * 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[StringTree], - processOr: Boolean = true - ): List[StringConstancyInformation] = { - val reduced = children.flatMap(reduceAcc) - val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) - val replaceElement = reduced.find(_.constancyType == StringConstancyType.REPLACE) - val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } - 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]() - - if (appendSci.isDefined) { - // The only difference between a Cond and an Or is how the possible strings look like - var possibleStrings = appendSci.get.possibleStrings - if (processOr) { - if (appendElements.tail.nonEmpty) { - possibleStrings = s"($possibleStrings)" - } - } else { - // IMPROVE dont wrap and immediately unwrap in () - if (possibleStrings.startsWith("(") && possibleStrings.endsWith(")")) { - possibleStrings = s"(${possibleStrings.substring(1, possibleStrings.length - 1)})?" - } else if (possibleStrings.startsWith("(") && possibleStrings.endsWith(")?")) { - possibleStrings = s"(${possibleStrings.substring(1, possibleStrings.length - 2)})?" - } else { - possibleStrings = s"(${appendSci.get.possibleStrings})?" - } - } - scis.append(StringConstancyInformation( - appendSci.get.constancyLevel, - appendSci.get.constancyType, - possibleStrings - )) - } - if (resetElement.isDefined) { - scis.append(resetElement.get) - } - if (replaceElement.isDefined) { - scis.append(replaceElement.get) - } - scis.toList - } - - /** - * This is a helper function which processes the `reduce` operation for [[StringTreeConcat]] - * elements. - */ - private def processReduceConcat(children: List[StringTree]): List[StringConstancyInformation] = { - val reducedLists = children.map(reduceAcc) - // Stores whether we deal with a flat or 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 / 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(nextSci) - } - } // 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 (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: StringTree): List[StringConstancyInformation] = { - subtree match { - case StringTreeRepetition(c, lowerBound, upperBound) => - val times = if (lowerBound.isDefined && upperBound.isDefined) - (upperBound.get - lowerBound.get).toString - else InfiniteRepetitionSymbol - val reduced = reduceAcc(c).headOption.getOrElse(StringConstancyInformation.lb) - List(StringConstancyInformation( - reduced.constancyLevel, - reduced.constancyType, - s"(${reduced.possibleStrings})$times" - )) - case StringTreeConcat(cs) => processReduceConcat(cs.toList) - case StringTreeOr(cs) => processReduceCondOrReduceOr(cs.toList) - case StringTreeCond(c) => processReduceCondOrReduceOr(List(c), processOr = false) - case StringTreeConst(sci) => List(sci) - } - } - - /** - * 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 [[StringTree]] with unique elements. - */ - private def removeDuplicateTreeValues(children: ListBuffer[StringTree]): ListBuffer[StringTree] = { - val seen = mutable.Map[StringConstancyInformation, Boolean]() - - children.flatMap[StringTree] { - case next @ StringTreeConst(sci) => - if (!seen.contains(sci)) { - seen += (sci -> true) - Some(next) - } else None - case other => Some(other) - } - } - - /** - * Accumulator function for simplifying a tree. - */ - private def simplifyAcc(subtree: StringTree): StringTree = { - subtree match { - case StringTreeOr(cs) => - // Flatten any nested StringTreeOr elements into this one - val newChildren = ListBuffer.empty[StringTree] - cs.foreach { - case nextC: StringTreeOr => newChildren.appendAll(simplifyAcc(nextC).children) - case c => newChildren.append(c) - } - StringTreeOr(removeDuplicateTreeValues(newChildren)) - 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)*). question isn't this q mark, see p.24? - 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 - } - } - - /** - * 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 = { - var newChildren = subtree.children.map(groupRepetitionElementsAcc) - - val repetitionElements = newChildren.collect { case str: StringTreeRepetition => str } - - // 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.appendAll(newChildren) - subtree - } else { - val childrenOfReps = repetitionElements.map(_.child) - val newRepElement = StringTreeRepetition(StringTreeOr(childrenOfReps)) - val indexFirstChild = newChildren.indexOf(repetitionElements.head) - - newChildren = newChildren.filter { !_.isInstanceOf[StringTreeRepetition] } - newChildren.insert(indexFirstChild, newRepElement) - if (newChildren.length == 1) { - newChildren.head - } else { - subtree match { - case _: StringTreeOr => StringTreeOr(newChildren) - case _ => StringTreeConcat(newChildren) - } - } - } - } - - subtree match { - case sto: StringTreeOr => processConcatOrOrCase(sto) - case stc: StringTreeConcat => processConcatOrOrCase(stc) - case StringTreeCond(child) => StringTreeCond(groupRepetitionElementsAcc(child)) - 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. - * - * @param preprocess If set to true, `this` tree will be preprocess, i.e., it will be - * simplified and repetition elements be grouped. Note that pre-processing - * changes `this` instance! - * @return A [[StringConstancyInformation]] instance that flatly describes this tree. - */ - 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)) - } - - /** - * Simplifies this tree. Currently, this means that when a (sub) tree has a - * [[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. - */ - 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 does not produce a - * semantically different tree! - */ - def groupRepetitionElements(): StringTree = groupRepetitionElementsAcc(this) -} - -/** - * [[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!
      - * - * `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 StringTreeRepetition( - child: StringTree, - lowerBound: Option[Int] = None, - upperBound: Option[Int] = None -) extends StringTree(ListBuffer(child)) - -/** - * [[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[StringTree] -) extends StringTree(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[StringTree] -) extends StringTree(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.
      - * - * In contrast to [[StringTreeOr]], [[StringTreeCond]] provides a way to express that a (sub) - * string may have (contain) a particular but not necessarily. - */ -case class StringTreeCond( - child: StringTree -) extends StringTree(ListBuffer(child)) - -/** - * [[StringTreeConst]]s are the only elements which are supposed to act as leafs within a - * [[StringTree]]. - * - * `sci` is a [[StringConstancyInformation]] instance that resulted from evaluating an - * expression and that represents part of the value(s) a string may have. - */ -case class StringTreeConst( - sci: StringConstancyInformation -) extends StringTree(ListBuffer()) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala new file mode 100644 index 0000000000..2e4f310ad0 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala @@ -0,0 +1,227 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package br +package fpcf +package properties +package string_definition + +import scala.collection.immutable.Seq +import scala.collection.mutable +import scala.util.Try +import scala.util.matching.Regex + +/** + * @author Maximilian Rüsch + */ +sealed trait StringTreeNode { + + val children: Seq[StringTreeNode] + + def toRegex: String + + def simplify: StringTreeNode + + def constancyLevel: StringConstancyLevel.Value + + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode + + def isNeutralElement: Boolean = false +} + +case class StringTreeRepetition( + child: StringTreeNode, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None +) extends StringTreeNode { + + override val children: Seq[StringTreeNode] = Seq(child) + + override def toRegex: String = { + (lowerBound, upperBound) match { + case (Some(lb), Some(ub)) => s"(${child.toRegex}){$lb,$ub}" + case (Some(lb), None) => s"(${child.toRegex}){$lb,}" + case _ => s"(${child.toRegex})*" + } + } + + override def simplify: StringTreeNode = { + val simplifiedChild = child.simplify + if (simplifiedChild.isNeutralElement) + StringTreeNeutralElement + else + this.copy(child = simplifiedChild) + } + + override def constancyLevel: StringConstancyLevel.Value = child.constancyLevel + + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + this.copy(child = child.replaceParameters(parameters)) +} + +case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends StringTreeNode { + + override def toRegex: String = { + children.size match { + case 0 => StringTreeNeutralElement.toRegex + case 1 => children.head.toRegex + case _ => s"${children.map(_.toRegex).reduceLeft((o, n) => s"$o$n")}" + } + } + + override def simplify: StringTreeNode = { + val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) + if (nonNeutralChildren.isEmpty) + StringTreeNeutralElement + else + StringTreeConcat(nonNeutralChildren) + } + + override def constancyLevel: StringConstancyLevel.Value = + children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineForConcat) + + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + StringTreeConcat(children.map(_.replaceParameters(parameters))) +} + +object StringTreeConcat { + def fromNodes(children: StringTreeNode*): StringTreeConcat = new StringTreeConcat(children) +} + +case class StringTreeOr(override val children: Seq[StringTreeNode]) extends StringTreeNode { + + override def toRegex: String = { + children.size match { + case 0 => throw new IllegalStateException("Tried to convert StringTreeOr with no children to a regex!") + case 1 => children.head.toRegex + case _ => s"(${children.map(_.toRegex).reduceLeft((o, n) => s"$o|$n")})" + } + } + + override def simplify: StringTreeNode = { + val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) + nonNeutralChildren.size match { + case 0 => StringTreeNeutralElement + case 1 => nonNeutralChildren.head + case _ => + var newChildren = Seq.empty[StringTreeNode] + nonNeutralChildren.foreach { + case orChild: StringTreeOr => newChildren :++= orChild.children + case child => newChildren :+= child + } + StringTreeOr(removeDuplicates(newChildren)) + } + } + + private def removeDuplicates(someChildren: Seq[StringTreeNode]): Seq[StringTreeNode] = { + val seen = mutable.HashSet.empty[String] + someChildren.flatMap { + case next @ StringTreeConst(string) => + if (!seen.contains(string)) { + seen += string + Some(next) + } else None + case other => Some(other) + } + } + + override def constancyLevel: StringConstancyLevel.Value = + children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) + + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + StringTreeOr(children.map(_.replaceParameters(parameters))) +} + +object StringTreeOr { + def fromNodes(children: StringTreeNode*): StringTreeOr = new StringTreeOr(children) +} + +case class StringTreeCond(child: StringTreeNode) extends StringTreeNode { + + override val children: Seq[StringTreeNode] = Seq(child) + + override def toRegex: String = { + val childRegex = child.toRegex + + // IMPROVE dont wrap and immediately unwrap in () + val resultingRegex = if (childRegex.startsWith("(") && childRegex.endsWith(")")) { + s"(${childRegex.substring(1, childRegex.length - 1)})?" + } else { + s"($childRegex)?" + } + + resultingRegex + } + + override def simplify: StringTreeNode = { + child.simplify match { + case condNode: StringTreeCond => condNode + case repetitionNode: StringTreeRepetition => repetitionNode + case node if node.isNeutralElement => StringTreeNeutralElement + case node => StringTreeCond(node) + } + } + + override def constancyLevel: StringConstancyLevel.Value = child.constancyLevel + + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + StringTreeCond(child.replaceParameters(parameters)) +} + +sealed trait SimpleStringTreeNode extends StringTreeNode { + + override final val children: Seq[StringTreeNode] = Seq.empty + + override final def simplify: StringTreeNode = this + + override def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this +} + +case class StringTreeConst(string: String) extends SimpleStringTreeNode { + override def toRegex: String = Regex.quoteReplacement(string) + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + + def isIntConst: Boolean = Try(string.toInt).isSuccess +} + +case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { + override def toRegex: String = ".*" + + override def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + parameters.getOrElse(index, this) + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC +} + +object StringTreeNeutralElement extends SimpleStringTreeNode { + override def toRegex: String = "" + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + + override def isNeutralElement: Boolean = true +} + +object StringTreeNull extends SimpleStringTreeNode { + // Using this element nested in some other element might lead to unexpected results... + override def toRegex: String = "^null$" + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT +} + +object StringTreeDynamicString extends SimpleStringTreeNode { + override def toRegex: String = ".*" + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC +} + +object StringTreeDynamicInt extends SimpleStringTreeNode { + override def toRegex: String = "^-?\\d+$" + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC +} + +object StringTreeDynamicFloat extends SimpleStringTreeNode { + override def toRegex: String = "^-?\\d*\\.{0,1}\\d+$" + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 5c7d6fc990..e3274d5711 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -19,7 +19,6 @@ import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler 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.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -111,10 +110,12 @@ trait StringAnalysis extends FPCFAnalysis { * @return Returns the final result. */ protected def computeFinalResult(state: State): Result = { - val finalSci = PathTransformer - .pathToStringTree(state.computedLeanPath)(state, ps) - .reduce(true) - Result(state.entity, StringConstancyProperty(finalSci)) + Result( + state.entity, + StringConstancyProperty(StringConstancyInformation( + tree = PathTransformer.pathToStringTree(state.computedLeanPath)(state, ps).simplify + )) + ) } protected def getInterimResult( @@ -132,11 +133,9 @@ trait StringAnalysis extends FPCFAnalysis { private def computeNewUpperBound(state: State): StringConstancyProperty = { if (state.computedLeanPath != null) { - StringConstancyProperty( - PathTransformer - .pathToStringTree(state.computedLeanPath)(state, ps) - .reduce(true) - ) + StringConstancyProperty(StringConstancyInformation( + tree = PathTransformer.pathToStringTree(state.computedLeanPath)(state, ps).simplify + )) } else { StringConstancyProperty.lb } @@ -192,8 +191,10 @@ trait StringAnalysis extends FPCFAnalysis { FlatPathElement(defSites.head) } else { // Create alternative branches with intermediate None-Type nested path elements - val children = defSites.map { ds => NestedPathElement(ListBuffer(FlatPathElement(ds)), None) } - NestedPathElement(ListBuffer.from(children), Some(NestedPathType.CondWithAlternative)) + NestedPathElement( + defSites.toIndexedSeq.map { ds => NestedPathElement(Seq(FlatPathElement(ds)), None) }, + Some(NestedPathType.CondWithAlternative) + ) } Path(List(element)) } @@ -327,30 +328,14 @@ object StringAnalysis { } } - /** - * 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 [[StringAnalysis.isSupportedType(String)]]. - */ 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 + def getDynamicStringInformationForNumberType(numberType: String): StringConstancyInformation = { + numberType match { + case "short" | "int" => StringConstancyInformation.dynamicInt + case "float" | "double" => StringConstancyInformation.dynamicFloat + case _ => StringConstancyInformation.lb } - StringConstancyInformation(StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 3dd53eb717..870073c1b2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -40,7 +40,7 @@ trait StringInterpreter[State <: ComputationState] { // replace with intermediate lattice result approach protected final def awaitAllFinalContinuation( depender: EPSDepender[T, State], - finalResult: Iterable[SomeFinalEP] => ProperPropertyComputationResult + finalResult: Seq[SomeFinalEP] => ProperPropertyComputationResult )(result: SomeEPS): ProperPropertyComputationResult = { if (result.isFinal) { val updatedDependees = depender.dependees.updated( @@ -49,7 +49,7 @@ trait StringInterpreter[State <: ComputationState] { ) if (updatedDependees.forall(_.isFinal)) { - finalResult(updatedDependees.asInstanceOf[Iterable[SomeFinalEP]]) + finalResult(updatedDependees.asInstanceOf[Seq[SomeFinalEP]]) } else { InterimResult.forLB( InterpretationHandler.getEntityFromDefSitePC(depender.pc)(depender.state), 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 589c03c477..af070573bb 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 @@ -24,13 +24,13 @@ case class BinaryExprInterpreter[State <: ComputationState]() extends StringInte *
    • [[ComputationalTypeInt]] *
    • [[ComputationalTypeFloat]]
    • * - * For all other expressions, a [[StringConstancyInformation.getNeutralElement]] will be returned. + * For all other expressions, a [[StringConstancyInformation.neutralElement]] will be returned. */ def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val sci = instr.cTpe match { - case ComputationalTypeInt => InterpretationHandler.getConstancyInfoForDynamicInt - case ComputationalTypeFloat => InterpretationHandler.getConstancyInfoForDynamicFloat - case _ => StringConstancyInformation.getNeutralElement + case ComputationalTypeInt => StringConstancyInformation.dynamicInt + case ComputationalTypeFloat => StringConstancyInformation.dynamicFloat + case _ => StringConstancyInformation.neutralElement } computeFinalResult(pc, sci) } 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 d9675c0292..7f57a753f0 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 @@ -13,8 +13,8 @@ import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.ObjectType 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.string_definition.StringTreeDynamicString import org.opalj.fpcf.ProperPropertyComputationResult /** @@ -199,42 +199,8 @@ object InterpretationHandler { news.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 getConstancyInfoForDynamicInt: 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 getConstancyInfoForDynamicFloat: StringConstancyInformation = - StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.FloatValue - ) - - /** - * @return Returns a [[StringConstancyInformation]] 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( - StringConstancyLevel.DYNAMIC, - StringConstancyType.REPLACE, - StringConstancyInformation.UnknownWordSymbol - ) + StringConstancyInformation(StringConstancyType.REPLACE, StringTreeDynamicString) def getEntityFromDefSite(defSite: Int)(implicit state: ComputationState): DefSiteEntity = getEntityFromDefSitePC(pcOfDefSite(defSite)(state.tac.stmts)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index 71149da1fb..c03c0df388 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -7,8 +7,8 @@ package string_analysis package interpretation 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.string_definition.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult /** @@ -19,20 +19,18 @@ case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends override type T = SimpleValueConst def interpret(expr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { - val sci = expr match { - case ic: IntConst => - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, ic.value.toString) - case fc: FloatConst => - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, fc.value.toString) - case dc: DoubleConst => - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, dc.value.toString) - case lc: LongConst => - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, lc.value.toString) - case sc: StringConst => - StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, sc.value) - case _ => - StringConstancyInformation.getNeutralElement + val treeOpt = expr match { + case ic: IntConst => Some(StringTreeConst(ic.value.toString)) + case fc: FloatConst => Some(StringTreeConst(fc.value.toString)) + case dc: DoubleConst => Some(StringTreeConst(dc.value.toString)) + case lc: LongConst => Some(StringTreeConst(lc.value.toString)) + case sc: StringConst => Some(StringTreeConst(sc.value)) + case _ => None } + + val sci = treeOpt + .map(StringConstancyInformation(StringConstancyType.APPEND, _)) + .getOrElse(StringConstancyInformation.neutralElement) computeFinalResult(pc, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 35b8afb900..e8cce5bcb8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -43,15 +43,15 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertySto ) ) } else { - finalResult(pc)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } } - private def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit + private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: State ): ProperPropertyComputationResult = { var resultSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + _.asFinal.p.asInstanceOf[StringConstancyProperty].sci }) if (resultSci.isTheNeutralElement) { resultSci = StringConstancyInformation.lb diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala index 9726284762..9e41c2610c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -136,17 +136,16 @@ trait L0FunctionCallInterpreter[State <: L0ComputationState] continuation(state, callState) ) } else { - val parameterScis = callState.paramDependees.map { param => - StringConstancyInformation.reduceMultiple(param.map { - _.asFinal.p.sci - }) - } + val parameterScis = callState.paramDependees.zipWithIndex.map { + case (params, index) => + (index, StringConstancyInformation.reduceMultiple(params.map(_.asFinal.p.sci))) + }.toMap val methodScis = callState.calleeMethods.map { m => if (callState.hasUnresolvableReturnValue(m)) { StringConstancyInformation.lb } else { StringConstancyInformation.reduceMultiple(callState.returnDependees(m).map { - _.asFinal.p.sci.fillInParameters(parameterScis) + _.asFinal.p.sci.replaceParameters(parameterScis) }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index d7a64e5596..9de987d7c4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -39,7 +39,7 @@ class L0InterpretationHandler[State <: L0ComputationState]()( case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) // Currently unsupported case Assignment(_, _, _: GetField[V]) => @@ -64,7 +64,7 @@ class L0InterpretationHandler[State <: L0ComputationState]()( case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) case _ => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index 87eb99aa40..60b5ae1d2b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -60,11 +60,11 @@ class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) ) ) } else { - finalResult(pc)(allResults.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(allResults.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } } - private def finalResult(pc: Int)(results: Iterable[SomeFinalEP])(implicit state: State): Result = { + private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: State): Result = { val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) val sci = if (resultsScis.forall(_.isTheNeutralElement)) { // It might be that there are no results; in such a case, set the string information to the lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 509aaaba90..598258b38e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -28,20 +28,20 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr, pc) - case _ => computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + case _ => computeFinalResult(pc, StringConstancyInformation.neutralElement) } } private def interpretInit(init: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { init.params.size match { - case 0 => computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + case 0 => computeFinalResult(pc, StringConstancyInformation.neutralElement) case _ => // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val results = init.params.head.asVar.definedBy.toList.map { ds => ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) } if (results.forall(_.isFinal)) { - finalResult(init.pc)(results.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } else { InterimResult.forLB( InterpretationHandler.getEntityFromDefSitePC(pc), @@ -56,7 +56,7 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr } } - private def finalResult(pc: Int)(results: Iterable[SomeEPS])(implicit + private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit state: State ): Result = computeFinalResult( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index ff7b63479e..c22f7d464b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -7,12 +7,11 @@ package string_analysis package l0 package interpretation -import scala.util.Try - import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore @@ -74,15 +73,16 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L val ps: PropertyStore def processStringValueOf(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { - def finalResult(results: Iterable[SomeFinalEP]): Result = { + def finalResult(results: Seq[SomeFinalEP]): Result = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } 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 { - sci + sci.tree match { + case const: StringTreeConst if const.isIntConst => + sci.copy(tree = StringTreeConst(const.string.toInt.toChar.toString)) + case _ => + sci } } } else { @@ -105,7 +105,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L ) ) } else { - finalResult(results.asInstanceOf[Iterable[SomeFinalEP]]) + finalResult(results.asInstanceOf[Seq[SomeFinalEP]]) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 769ca49db1..8187d48f5c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -7,8 +7,6 @@ package string_analysis package l0 package interpretation -import scala.util.Try - import org.opalj.br.ComputationalTypeDouble import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -19,6 +17,8 @@ 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.fpcf.properties.string_definition.StringTreeConcat +import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -80,16 +80,9 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( case obj: ObjectType if obj == ObjectType.String => interpretArbitraryCall(instr, pc) case FloatType | DoubleType => - computeFinalResult( - pc, - StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.FloatValue - ) - ) + computeFinalResult(pc, StringConstancyInformation.dynamicFloat) case _ => - computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + computeFinalResult(pc, StringConstancyInformation.neutralElement) } } } @@ -235,22 +228,21 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta _.asFinal.p.sci }) val valueSci = transformAppendValueResult( - appendState.valueDependees.asInstanceOf[Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]]] + appendState.valueDependees.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]] ) computeFinalResult( appendState.defSitePC, StringConstancyInformation( - StringConstancyLevel.determineForConcat(receiverSci.constancyLevel, valueSci.constancyLevel), StringConstancyType.APPEND, - receiverSci.possibleStrings + valueSci.possibleStrings + StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree) ) ) } } private def transformAppendValueResult( - results: Iterable[FinalEP[DefSiteEntity, StringConstancyProperty]] + results: Seq[FinalEP[DefSiteEntity, StringConstancyProperty]] )(implicit appendState: AppendCallState): StringConstancyInformation = { val sciValues = results.map(_.p.sci) val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) @@ -261,12 +253,11 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta newValueSci.constancyLevel == StringConstancyLevel.CONSTANT && sciValues.exists(!_.isTheNeutralElement) ) { - val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci => - if (Try(sci.possibleStrings.toInt).isSuccess) { - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) - } else { + val charSciValues = sciValues map { + case sci @ StringConstancyInformation(_, const: StringTreeConst) if const.isIntConst => + sci.copy(tree = StringTreeConst(const.string.toInt.toChar.toString)) + case sci => sci - } } StringConstancyInformation.reduceMultiple(charSciValues) } else { @@ -276,7 +267,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta if (newValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { newValueSci } else { - InterpretationHandler.getConstancyInfoForDynamicFloat + StringConstancyInformation.dynamicFloat } case _ => newValueSci @@ -312,17 +303,17 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation ) ) } else { - computeFinalSubstringCallResult(substringCall, pc)(receiverResults.asInstanceOf[Iterable[SomeFinalEP]]) + computeFinalSubstringCallResult(substringCall, pc)(receiverResults.asInstanceOf[Seq[SomeFinalEP]]) } } private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( - results: Iterable[SomeFinalEP] + results: Seq[SomeFinalEP] )(implicit state: State): Result = { val receiverSci = StringConstancyInformation.reduceMultiple(results.map { _.p.asInstanceOf[StringConstancyProperty].sci }) - if (receiverSci.isTheNeutralElement || receiverSci.isComplex) { + if (!receiverSci.tree.isInstanceOf[StringTreeConst]) { // We cannot yet interpret substrings of mixed values computeFinalResult(pc, StringConstancyInformation.lb) } else { @@ -334,9 +325,10 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation computeFinalResult( pc, StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.REPLACE, - receiverSci.possibleStrings.substring(intValue.value) + StringTreeConst( + receiverSci.tree.asInstanceOf[StringTreeConst].string.substring(intValue.value) + ) ) ) case _ => @@ -349,9 +341,13 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation computeFinalResult( pc, StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, - receiverSci.possibleStrings.substring(firstIntValue.value, secondIntValue.value) + StringTreeConst( + receiverSci.tree.asInstanceOf[StringTreeConst].string.substring( + firstIntValue.value, + secondIntValue.value + ) + ) ) ) case _ => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 606876f7e3..adacae10d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -8,7 +8,6 @@ package l0 package interpretation 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.fpcf.ProperPropertyComputationResult @@ -30,13 +29,13 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends * *
    * - * For all other calls, a [[StringConstancyInformation.getNeutralElement]] will be returned. + * For all other calls, a [[StringConstancyInformation.neutralElement]] will be returned. */ override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength - case "setLength" => StringConstancyInformation(StringConstancyLevel.CONSTANT, StringConstancyType.RESET) - case _ => StringConstancyInformation.getNeutralElement + case "setLength" => StringConstancyInformation(StringConstancyType.RESET) + case _ => StringConstancyInformation.neutralElement } computeFinalResult(pc, sci) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index ad27e94696..de7f2048b0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -15,7 +15,6 @@ import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context 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.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult @@ -26,7 +25,6 @@ import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.properties.TACAI /** @@ -70,8 +68,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) - val iHandler = - L1InterpretationHandler[CState](project, ps) + val iHandler = L1InterpretationHandler[CState](project, ps) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { @@ -168,22 +165,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } } - val sci = - if (attemptFinalResultComputation - && state.dependees.isEmpty - && computeResultsForPath(state.computedLeanPath)(state) - ) { - PathTransformer - .pathToStringTree(state.computedLeanPath)(state, ps) - .reduce(true) - } else { - StringConstancyInformation.lb - } - if (state.dependees.nonEmpty) { getInterimResult(state, iHandler) } else { - Result(state.entity, StringConstancyProperty(sci)) + computeFinalResult(state) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 69e0da0834..7404a94344 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -13,7 +13,9 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider 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.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string_definition.StringTreeNull +import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.fpcf.EOptionP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -92,7 +94,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( * [[L1StringAnalysis]] 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]]. + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]]. */ override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { // TODO: The approximation of fields might be outsourced into a dedicated analysis. Then, one could add a @@ -109,12 +111,12 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( if (writeAccesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value - val sci = StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - possibleStrings = - s"(${StringConstancyInformation.NullStringValue}|${StringConstancyInformation.UnknownWordSymbol})" + return computeFinalResult( + pc, + StringConstancyInformation( + tree = StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) + ) ) - return computeFinalResult(pc, sci) } implicit val accessState: FieldReadState = FieldReadState(pc) @@ -155,7 +157,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( // could be refined by only setting the null element if no statement is guaranteed to be executed prior // to the field read if (!accessState.hasInit) { - scis = scis :+ StringConstancyInformation.getNullElement + scis = scis :+ StringConstancyInformation.nullElement } // If an access could not be resolved, append a dynamic element if (accessState.hasUnresolvableAccess) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index a358b27bd7..ca8c1f0410 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -54,7 +54,7 @@ class L1InterpretationHandler[State <: L1ComputationState]( case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) case Assignment(_, _, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( @@ -91,7 +91,7 @@ class L1InterpretationHandler[State <: L1ComputationState]( L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) case _ => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getNeutralElement) + StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) } } } 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 9b935dc785..e53c76099f 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 @@ -399,10 +399,9 @@ abstract class AbstractPathFinder(tac: TAC) { end: Int, fill: Boolean ): (Path, List[(Int, Int)]) = { - val path = ListBuffer[SubPath]() - if (fill) { - start.to(end).foreach(i => path.append(FlatPathElement(i))) - } + val path = if (fill) { + start.to(end).map(FlatPathElement.apply) + } else Seq.empty[SubPath] (Path(List(NestedPathElement(path, Some(NestedPathType.Repetition)))), List((start, end))) } @@ -483,15 +482,14 @@ abstract class AbstractPathFinder(tac: TAC) { startEndPairs.append((startEndPairs.last._2 + 1, end)) } - val subPaths = ListBuffer[SubPath]() - startEndPairs.foreach { + val subPaths = startEndPairs.toSeq.flatMap { case (startSubpath, endSubpath) => - val subpathElements = ListBuffer[SubPath]() - if (fill) { - subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement.apply)) - } + val subpathElements = if (fill) { + startSubpath.to(endSubpath).map(FlatPathElement.apply) + } else Seq.empty[SubPath] if (!fill || subpathElements.nonEmpty) - subPaths.append(NestedPathElement(subpathElements, None)) + Some(NestedPathElement(subpathElements, None)) + else None } val pathTypeToUse = @@ -536,11 +534,10 @@ abstract class AbstractPathFinder(tac: TAC) { startEndPairs.append((previousStart, end)) } - val subPaths: ListBuffer[SubPath] = startEndPairs.map { pair => - val subpathElements = ListBuffer[SubPath]() - if (fill) { - subpathElements.appendAll(Range.inclusive(pair._1, pair._2).map(FlatPathElement.apply)) - } + val subPaths = startEndPairs.toSeq.map { pair => + val subpathElements = if (fill) { + Range.inclusive(pair._1, pair._2).map(FlatPathElement.apply) + } else Seq.empty[SubPath] NestedPathElement(subpathElements, None) } (Path(List(NestedPathElement(subPaths, Some(pathType)))), startEndPairs.toList) @@ -651,20 +648,18 @@ abstract class AbstractPathFinder(tac: TAC) { startEndPairs.append((cn.endPC, endOfCatch)) } - val subPaths = ListBuffer[SubPath]() - startEndPairs.foreach { + var subPaths: Seq[SubPath] = startEndPairs.toSeq.map { case (startSubpath, endSubpath) => - val subpathElements = ListBuffer[SubPath]() - subPaths.append(NestedPathElement(subpathElements, None)) - if (fill) { - subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement.apply)) - } + val subpathElements = if (fill) { + startSubpath.to(endSubpath).map(FlatPathElement.apply) + } else Seq.empty[SubPath] + NestedPathElement(subpathElements, None) } // 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(FlatPathElement.apply)) + subPaths = subPaths ++ (startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map(FlatPathElement.apply) } ( @@ -680,11 +675,8 @@ abstract class AbstractPathFinder(tac: TAC) { numInnerElements: Int, elementType: NestedPathType.Value ): NestedPathElement = { - val outerNested = NestedPathElement(ListBuffer(), Some(elementType)) - for (_ <- 0.until(numInnerElements)) { - outerNested.element.append(NestedPathElement(ListBuffer(), None)) - } - outerNested + val innerElements = 0.until(numInnerElements).map(_ => NestedPathElement(Seq.empty, None)) + NestedPathElement(innerElements, Some(elementType)) } /** @@ -1212,8 +1204,7 @@ abstract class AbstractPathFinder(tac: TAC) { if (children.isEmpty) { // Recursion anchor: Build path for the correct type val (subpath, _) = buildPathForElement(nextTopCsInfo, fill = true) - // Control structures consist of only one element (NestedPathElement), thus "head" - // is enough + // Control structures consist of only one element (NestedPathElement), thus "head" is enough finalPath.append(subpath.elements.head) } else { val startIndex = nextTopCsInfo._1 @@ -1226,14 +1217,17 @@ abstract class AbstractPathFinder(tac: TAC) { val npe = subpath.elements.head.asInstanceOf[NestedPathElement] val isRepElement = npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == NestedPathType.Repetition + var newElements = npe.element var lastInsertedIndex = 0 childrenPath.elements.foreach { nextEle => if (isRepElement) { - npe.element.append(nextEle) + newElements :+= nextEle } else { - if (insertIndex < npe.element.length) { - npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( - nextEle + if (insertIndex < newElements.length) { + val innerNpe = newElements(insertIndex).asInstanceOf[NestedPathElement] + newElements = newElements.updated( + insertIndex, + NestedPathElement(innerNpe.element :+ nextEle, innerNpe.elementType) ) } } @@ -1249,34 +1243,39 @@ abstract class AbstractPathFinder(tac: TAC) { } } // Fill the current NPE if necessary - val currentToInsert = ListBuffer[FlatPathElement]() if (insertIndex < startEndPairs.length) { - currentToInsert.appendAll((lastInsertedIndex + 1).to( - startEndPairs(insertIndex)._2 - ).map(FlatPathElement.apply)) + val currentToInsert = + (lastInsertedIndex + 1).to(startEndPairs(insertIndex)._2).map(FlatPathElement.apply) if (isRepElement) { - npe.element.appendAll(currentToInsert) + newElements ++= currentToInsert } else { - var insertPos = npe.element(insertIndex).asInstanceOf[NestedPathElement] - insertPos.element.appendAll(currentToInsert) + val innerNpe = newElements(insertIndex).asInstanceOf[NestedPathElement] + newElements = newElements.updated( + insertIndex, + NestedPathElement(innerNpe.element ++ currentToInsert, innerNpe.elementType) + ) 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.apply) + val innerNpe = newElements(i).asInstanceOf[NestedPathElement] + newElements = newElements.updated( + i, + NestedPathElement( + innerNpe.element ++ + startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement.apply), + innerNpe.elementType + ) ) } } } // Make sure to have no empty lists - val subPathNpe = subpath.elements.head.asInstanceOf[NestedPathElement] val subPathToAdd = NestedPathElement( - subPathNpe.element.filter { + newElements.filter { case npe: NestedPathElement => npe.element.nonEmpty case _ => true }, - subPathNpe.elementType + npe.elementType ) finalPath.append(subPathToAdd) } 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 7588c50733..ed2614eec2 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 @@ -88,7 +88,7 @@ case object NestedPathType extends Enumeration { * possible, i.e., when they compute / have this information. */ case class NestedPathElement( - element: ListBuffer[SubPath], + element: Seq[SubPath], elementType: Option[NestedPathType.Value] ) extends SubPath @@ -147,11 +147,11 @@ 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. */ - @tailrec private def removeOuterBranching(npe: NestedPathElement): ListBuffer[SubPath] = { + @tailrec private def removeOuterBranching(npe: NestedPathElement): Seq[SubPath] = { if (npe.element.tail.isEmpty) { npe.element.head match { case innerNpe: NestedPathElement => removeOuterBranching(innerNpe) - case fpe: SubPath => ListBuffer[SubPath](fpe) + case fpe: SubPath => Seq(fpe) } } else { npe.element @@ -166,18 +166,18 @@ case class Path(elements: List[SubPath]) { * well. */ private def stripUnnecessaryBranches(npe: NestedPathElement, endSite: Int): NestedPathElement = { - npe.element.foreach { - case innerNpe: NestedPathElement => - if (innerNpe.elementType.isEmpty) { - if (!containsPathElementWithPC(innerNpe, endSite)) { - innerNpe.element.clear() - } + val strippedElements = npe.element.map { + case innerNpe @ NestedPathElement(_, elementType) if elementType.isEmpty => + if (!containsPathElementWithPC(innerNpe, endSite)) { + NestedPathElement(Seq.empty, None) } else { - stripUnnecessaryBranches(innerNpe, endSite) + innerNpe } - case _ => + case innerNpe: NestedPathElement => + stripUnnecessaryBranches(innerNpe, endSite) + case pe => pe } - npe + NestedPathElement(strippedElements, npe.elementType) } /** @@ -213,13 +213,13 @@ case class Path(elements: List[SubPath]) { if (leanedSubPath.isDefined) { elements.append(leanedSubPath.get) } else if (keepAlternativeBranches) { - elements.append(NestedPathElement(ListBuffer[SubPath](), None)) + elements.append(NestedPathElement(Seq.empty, None)) } case e => throw new IllegalStateException(s"Unexpected sub path element found: $e") } if (elements.nonEmpty) { - Some(NestedPathElement(elements, toProcess.elementType)) + Some(NestedPathElement(elements.toSeq, toProcess.elementType)) } else { None } @@ -259,7 +259,7 @@ case class Path(elements: List[SubPath]) { case _ => true } }.map { s => (pcOfDefSite(s), ()) }.toMap - var leanPath = ListBuffer[SubPath]() + val leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.toArray.max elements.foreach { @@ -281,7 +281,8 @@ case class Path(elements: List[SubPath]) { case npe: NestedPathElement if npe.elementType.get == NestedPathType.Repetition || npe.element.tail.isEmpty => - leanPath = removeOuterBranching(npe) + leanPath.clear() + leanPath.appendAll(removeOuterBranching(npe)) case _ => } } else { @@ -309,9 +310,9 @@ object Path { * exists, [[FlatPathElement.invalid]] is returned. */ @tailrec def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { - npe.element.last match { - case fpe: FlatPathElement => fpe - case npe: NestedPathElement => + npe.element.lastOption match { + case Some(fpe: FlatPathElement) => fpe + case Some(npe: NestedPathElement) => npe.element.last match { case fpe: FlatPathElement => fpe case innerNpe: NestedPathElement => getLastElementInNPE(innerNpe) 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 8f388a31bd..3aa3ea6805 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 @@ -6,14 +6,12 @@ package analyses package string_analysis package preprocessing -import scala.collection.mutable.ListBuffer - import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringTree 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.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string_definition.StringTreeNode import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition import org.opalj.fpcf.FinalP @@ -21,7 +19,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Transforms a [[Path]] into a [[org.opalj.br.fpcf.properties.string_definition.StringTree]]. + * Transforms a [[Path]] into a string tree of [[StringTreeNode]]s. * * @author Maximilian Rüsch */ @@ -33,14 +31,14 @@ object PathTransformer { private def pathToTreeAcc[State <: ComputationState](subpath: SubPath)(implicit state: State, ps: PropertyStore - ): Option[StringTree] = { + ): Option[StringTreeNode] = { subpath match { case fpe: FlatPathElement => val sci = ps(InterpretationHandler.getEntityFromDefSitePC(fpe.pc), StringConstancyProperty.key) match { case FinalP(scp) => scp.sci case _ => StringConstancyInformation.lb } - Option.unless(sci.isTheNeutralElement)(StringTreeConst(sci)) + Option.unless(sci.isTheNeutralElement)(sci.tree) case npe: NestedPathElement => if (npe.elementType.isDefined) { npe.elementType.get match { @@ -94,26 +92,26 @@ object PathTransformer { } /** - * Takes a [[Path]] and transforms it into a [[StringTree]]. This implies an interpretation of + * Takes a [[Path]] and transforms it into a [[StringTreeNode]]. 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. * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed - * [[org.opalj.br.fpcf.properties.string_definition.StringTree]] will be returned. Note that + * [[StringTreeNode]] 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[State <: ComputationState](path: Path)(implicit state: State, ps: PropertyStore - ): StringTree = { - val tree = path.elements.size match { + ): StringTreeNode = { + path.elements.size match { 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).getOrElse(StringTreeConst(StringConstancyInformation.lb)) + pathToTreeAcc(path.elements.head).getOrElse(StringTreeDynamicString) case _ => - val children = ListBuffer.from(path.elements.flatMap { pathToTreeAcc(_) }) + val children = path.elements.flatMap { pathToTreeAcc(_) } if (children.size == 1) { // The concatenation of one child is the child itself children.head @@ -121,6 +119,5 @@ object PathTransformer { StringTreeConcat(children) } } - tree } } From c7ee18286e6fde6582de143e6d5f5cd3f9957be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 14 Mar 2024 00:32:06 +0100 Subject: [PATCH 386/583] Move control of callees to using interpreter --- .../l1/L1ComputationState.scala | 10 --- .../string_analysis/l1/L1StringAnalysis.scala | 39 +-------- .../interpretation/L1StringInterpreter.scala | 31 +------ .../L1VirtualFunctionCallInterpreter.scala | 82 +++++++++++++++++-- 4 files changed, 77 insertions(+), 85 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala index 1dac9b9b40..1025c76992 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala @@ -6,20 +6,10 @@ package analyses package string_analysis package l1 -import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.properties.Context -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState trait L1ComputationState extends L0ComputationState { val methodContext: Context - - var calleesDependee: Option[EOptionP[DefinedMethod, Callees]] = _ - - /** - * Callees information regarding the declared method that corresponds to the entity's method - */ - var callees: Callees = _ } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index de7f2048b0..4f0d2d9e90 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -16,13 +16,11 @@ import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI @@ -67,7 +65,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { override def analyze(data: SContext): ProperPropertyComputationResult = { val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) - val state = CState(dm, data, contextProvider.newContext(declaredMethods(data._2))) + val state = CState(dm, data, contextProvider.newContext(dm)) val iHandler = L1InterpretationHandler[CState](project, ps) val tacaiEOptP = ps(data._2, TACAI.key) @@ -83,13 +81,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state.tac = tacaiEOptP.ub.tac.get - val calleesEOptP = ps(dm, Callees.key) - if (calleesEOptP.hasNoUBP) { - state.calleesDependee = Some(calleesEOptP) - return getInterimResult(state, iHandler) - } - - state.callees = calleesEOptP.ub determinePossibleStrings(state, iHandler) } @@ -106,7 +97,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted - if (state.tac == null || state.callees == null) { + if (state.tac == null) { return getInterimResult(state, iHandler) } @@ -171,32 +162,6 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { computeFinalResult(state) } } - - /** - * 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. - * @return Returns a final result if (already) available. Otherwise, an intermediate result will be returned. - */ - override protected def continuation( - state: State, - iHandler: InterpretationHandler[State] - )(eps: SomeEPS): ProperPropertyComputationResult = { - state.dependees = state.dependees.filter(_.e != eps.e) - - eps match { - case FinalP(callees: Callees) if eps.pk.equals(Callees.key) => - state.callees = callees - if (state.dependees.isEmpty) { - determinePossibleStrings(state, iHandler) - } else { - getInterimResult(state, iHandler) - } - case _ => - super.continuation(state, iHandler)(eps) - } - } } object L1StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala index ad4eaee49a..c43754ca07 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala @@ -7,38 +7,9 @@ package string_analysis package l1 package interpretation -import scala.collection.mutable.ListBuffer - -import org.opalj.br.DefinedMethod -import org.opalj.br.Method -import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.StringInterpreter /** * @author Maximilian Rüsch */ -trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] { - - /** - * 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 getMethodsForPC(pc: Int)( - implicit - state: State, - ps: PropertyStore, - contextProvider: ContextProvider - ): (List[Method], Boolean) = { - var hasMethodWithUnknownBody = false - val methods = ListBuffer[Method]() - - state.callees.callees(state.methodContext, pc).map(_.method).foreach { - case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) - case _ => hasMethodWithUnknownBody = true - } - - (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) - } -} +trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 96acff0c63..fbac6e81f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -7,10 +7,22 @@ package string_analysis package l1 package interpretation +import scala.collection.mutable.ListBuffer + +import org.opalj.br.DefinedMethod +import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider +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.fpcf.EOptionP +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI @@ -30,19 +42,73 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( override type T = VirtualFunctionCall[V] + private case class CalleeDepender( + pc: Int, + var calleeDependee: EOptionP[DefinedMethod, Callees] + ) + override protected def interpretArbitraryCall(instr: T, pc: Int)( implicit state: State ): ProperPropertyComputationResult = { - // IMPROVE add some uncertainty element if methods with unknown body exist - val (methods, _) = getMethodsForPC(instr.pc) - if (methods.isEmpty) { - return computeFinalResult(pc, StringConstancyInformation.lb) + val depender = CalleeDepender(pc, ps(state.dm, Callees.key)) + + depender.calleeDependee match { + case FinalP(c: Callees) => + val methods = getMethodsFromCallees(depender.pc, state, c) + if (methods.isEmpty) { + computeFinalResult(pc, StringConstancyInformation.lb) + } else { + val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap + val callState = FunctionCallState(pc, tacDependees.keys.toSeq, tacDependees) + callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) + + interpretArbitraryCallToMethods(state, callState) + } + + case _ => + InterimResult.forUB( + InterpretationHandler.getEntityFromDefSitePC(pc), + StringConstancyProperty.ub, + Set(depender.calleeDependee), + continuation(state, depender) + ) } + } - val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val callState = FunctionCallState(pc, tacDependees.keys.toSeq, tacDependees) - callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) + private def continuation(state: State, depender: CalleeDepender)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case FinalP(c: Callees) => + val methods = getMethodsFromCallees(depender.pc, state, c) + if (methods.isEmpty) { + computeFinalResult(depender.pc, StringConstancyInformation.lb)(state) + } else { + val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap + val callState = FunctionCallState(depender.pc, tacDependees.keys.toSeq, tacDependees) + callState.setParamDependees(evaluateParameters(getParametersForPC(depender.pc)(state))(state)) - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(state, callState) + } + + case UBP(_: Callees) => + depender.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] + InterimResult.forUB( + InterpretationHandler.getEntityFromDefSitePC(depender.pc)(state), + StringConstancyProperty.ub, + Set(depender.calleeDependee), + continuation(state, depender) + ) + + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") + } + } + + private def getMethodsFromCallees(pc: Int, state: State, callees: Callees): Seq[Method] = { + val methods = ListBuffer[Method]() + // IMPROVE only process newest callees + callees.callees(state.methodContext, pc).map(_.method).foreach { + case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) + case _ => // IMPROVE add some uncertainty element if methods with unknown body exist + } + methods.sortBy(_.classFile.fqn).toList } } From b00b2fb2a6c0449ed95db16abaac5a69710c47a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 14 Mar 2024 00:35:29 +0100 Subject: [PATCH 387/583] Remove method context from l1 state responsibility --- .../string_analysis/l1/L1ComputationState.scala | 15 --------------- .../string_analysis/l1/L1StringAnalysis.scala | 14 ++++++++------ .../l1/interpretation/L1StringInterpreter.scala | 15 --------------- .../L1VirtualFunctionCallInterpreter.scala | 12 +++++++----- 4 files changed, 15 insertions(+), 41 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala deleted file mode 100644 index 1025c76992..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1ComputationState.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 - -import org.opalj.br.fpcf.properties.Context -import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState - -trait L1ComputationState extends L0ComputationState { - - val methodContext: Context -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 4f0d2d9e90..86fe470402 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -12,7 +12,6 @@ import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.FinalEP @@ -22,9 +21,14 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI +trait L1ComputationState extends L0ComputationState + +trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] + /** * 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''. @@ -53,9 +57,8 @@ import org.opalj.tac.fpcf.properties.TACAI class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { protected[l1] case class CState( - override val dm: DefinedMethod, - override val entity: (SEntity, Method), - override val methodContext: Context + override val dm: DefinedMethod, + override val entity: (SEntity, Method) ) extends L1ComputationState override type State = CState @@ -63,9 +66,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) override def analyze(data: SContext): ProperPropertyComputationResult = { - val dm = declaredMethods(data._2) // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) - val state = CState(dm, data, contextProvider.newContext(dm)) + val state = CState(declaredMethods(data._2), data) val iHandler = L1InterpretationHandler[CState](project, ps) val tacaiEOptP = ps(data._2, TACAI.key) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala deleted file mode 100644 index c43754ca07..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1StringInterpreter.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package l1 -package interpretation - -import org.opalj.tac.fpcf.analyses.string_analysis.StringInterpreter - -/** - * @author Maximilian Rüsch - */ -trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index fbac6e81f9..6d59be9d0f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -12,6 +12,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -44,17 +45,18 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( private case class CalleeDepender( pc: Int, + methodContext: Context, var calleeDependee: EOptionP[DefinedMethod, Callees] ) override protected def interpretArbitraryCall(instr: T, pc: Int)( implicit state: State ): ProperPropertyComputationResult = { - val depender = CalleeDepender(pc, ps(state.dm, Callees.key)) + val depender = CalleeDepender(pc, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) depender.calleeDependee match { case FinalP(c: Callees) => - val methods = getMethodsFromCallees(depender.pc, state, c) + val methods = getMethodsFromCallees(depender.pc, depender.methodContext, c) if (methods.isEmpty) { computeFinalResult(pc, StringConstancyInformation.lb) } else { @@ -78,7 +80,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( private def continuation(state: State, depender: CalleeDepender)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(c: Callees) => - val methods = getMethodsFromCallees(depender.pc, state, c) + val methods = getMethodsFromCallees(depender.pc, depender.methodContext, c) if (methods.isEmpty) { computeFinalResult(depender.pc, StringConstancyInformation.lb)(state) } else { @@ -102,10 +104,10 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( } } - private def getMethodsFromCallees(pc: Int, state: State, callees: Callees): Seq[Method] = { + private def getMethodsFromCallees(pc: Int, context: Context, callees: Callees): Seq[Method] = { val methods = ListBuffer[Method]() // IMPROVE only process newest callees - callees.callees(state.methodContext, pc).map(_.method).foreach { + callees.callees(context, pc).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => // IMPROVE add some uncertainty element if methods with unknown body exist } From b45921b0e1b09c00d82fadba86c8433e35c929a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 14 Mar 2024 00:52:13 +0100 Subject: [PATCH 388/583] Collapse computation state into single class --- .../string_analysis/ComputationState.scala | 8 +--- .../string_analysis/StringAnalysis.scala | 25 +++++------ .../string_analysis/StringInterpreter.scala | 14 +++---- .../BinaryExprInterpreter.scala | 11 +---- .../InterpretationHandler.scala | 8 ++-- .../SimpleValueConstExprInterpreter.scala | 11 +---- .../string_analysis/l0/L0StringAnalysis.scala | 22 ++-------- .../L0ArrayAccessInterpreter.scala | 6 +-- .../L0FunctionCallInterpreter.scala | 12 +++--- .../L0InterpretationHandler.scala | 14 ++++--- .../L0NewArrayInterpreter.scala | 10 ++--- .../L0NonVirtualFunctionCallInterpreter.scala | 8 ++-- .../L0NonVirtualMethodCallInterpreter.scala | 10 ++--- .../L0StaticFunctionCallInterpreter.scala | 23 +++++------ .../L0VirtualFunctionCallInterpreter.scala | 41 ++++++++++--------- .../L0VirtualMethodCallInterpreter.scala | 4 +- .../string_analysis/l1/L1StringAnalysis.scala | 24 ++--------- .../L1FieldReadInterpreter.scala | 13 +++--- .../L1InterpretationHandler.scala | 14 ++++--- .../L1VirtualFunctionCallInterpreter.scala | 15 ++++--- .../preprocessing/PathTransformer.scala | 8 ++-- 21 files changed, 127 insertions(+), 174 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 7e44e3ef8e..a2cd896af7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -19,13 +19,7 @@ import org.opalj.tac.fpcf.properties.TACAI * 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. */ -trait ComputationState { - val dm: DefinedMethod - - /** - * The entity for which the analysis was started with. - */ - val entity: SContext +case class ComputationState(dm: DefinedMethod, entity: SContext) { /** * The Three-Address Code of the entity's method diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index e3274d5711..879937a99e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -45,8 +45,6 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringAnalysis extends FPCFAnalysis { - type State <: ComputationState - val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(data: SContext): ProperPropertyComputationResult @@ -57,8 +55,8 @@ trait StringAnalysis extends FPCFAnalysis { * [[InterimResult]] depending on whether other information needs to be computed first. */ protected[string_analysis] def determinePossibleStrings(implicit - state: State, - iHandler: InterpretationHandler[State] + state: ComputationState, + iHandler: InterpretationHandler ): ProperPropertyComputationResult /** @@ -71,8 +69,8 @@ trait StringAnalysis extends FPCFAnalysis { * be returned. */ protected[this] def continuation( - state: State, - iHandler: InterpretationHandler[State] + state: ComputationState, + iHandler: InterpretationHandler )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) if @@ -109,7 +107,7 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - protected def computeFinalResult(state: State): Result = { + protected def computeFinalResult(state: ComputationState): Result = { Result( state.entity, StringConstancyProperty(StringConstancyInformation( @@ -119,8 +117,8 @@ trait StringAnalysis extends FPCFAnalysis { } protected def getInterimResult( - state: State, - iHandler: InterpretationHandler[State] + state: ComputationState, + iHandler: InterpretationHandler ): InterimResult[StringConstancyProperty] = { InterimResult( state.entity, @@ -131,7 +129,7 @@ trait StringAnalysis extends FPCFAnalysis { ) } - private def computeNewUpperBound(state: State): StringConstancyProperty = { + private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { if (state.computedLeanPath != null) { StringConstancyProperty(StringConstancyInformation( tree = PathTransformer.pathToStringTree(state.computedLeanPath)(state, ps).simplify @@ -149,7 +147,7 @@ trait StringAnalysis extends FPCFAnalysis { * @param state The current state of the computation. * @return Returns `true` if all values computed for the path are final results. */ - protected def computeResultsForPath(p: Path)(implicit state: State): Boolean = { + protected def computeResultsForPath(p: Path)(implicit state: ComputationState): Boolean = { var hasFinalResult = true p.elements.foreach { case fpe: FlatPathElement => @@ -226,7 +224,7 @@ trait StringAnalysis extends FPCFAnalysis { * @return A mapping from dependent [[PUVar]]s to the [[FlatPathElement]] indices they occur in. */ protected def findDependentVars(path: Path, ignore: SEntity)( // We may need to register the old path with them - implicit state: State): mutable.LinkedHashMap[SEntity, Int] = { + implicit state: ComputationState): mutable.LinkedHashMap[SEntity, Int] = { val stmts = state.tac.stmts def findDependeesAcc(subpath: SubPath): ListBuffer[(SEntity, Int)] = { @@ -348,8 +346,7 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { PropertyBounds.lub(StringConstancyProperty) ) - type State <: ComputationState - override final type InitializationData = (StringAnalysis, InterpretationHandler[State]) + override final type InitializationData = (StringAnalysis, InterpretationHandler) override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 870073c1b2..c8dfa76cd4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation /** * @author Maximilian Rüsch */ -trait StringInterpreter[State <: ComputationState] { +trait StringInterpreter { type T <: ASTNode[V] @@ -31,15 +31,15 @@ trait StringInterpreter[State <: ComputationState] { * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given * instruction. */ - def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult + def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: State): Result = + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: ComputationState): Result = StringInterpreter.computeFinalResult(pc, sci) // IMPROVE remove this since awaiting all final is not really feasible // replace with intermediate lattice result approach protected final def awaitAllFinalContinuation( - depender: EPSDepender[T, State], + depender: EPSDepender[T, ComputationState], finalResult: Seq[SomeFinalEP] => ProperPropertyComputationResult )(result: SomeEPS): ProperPropertyComputationResult = { if (result.isFinal) { @@ -77,11 +77,11 @@ object StringInterpreter { Result(FinalEP(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty(sci))) } -trait ParameterEvaluatingStringInterpreter[State <: ComputationState] extends StringInterpreter[State] { +trait ParameterEvaluatingStringInterpreter extends StringInterpreter { val ps: PropertyStore - protected def getParametersForPC(pc: Int)(implicit state: State): Seq[Expr[V]] = { + protected def getParametersForPC(pc: Int)(implicit state: ComputationState): Seq[Expr[V]] = { state.tac.stmts(state.tac.pcToIndex(pc)) match { case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params case Assignment(_, _, fc: FunctionCall[V]) => fc.params @@ -90,7 +90,7 @@ trait ParameterEvaluatingStringInterpreter[State <: ComputationState] extends St } protected def evaluateParameters(params: Seq[Expr[V]])(implicit - state: State + state: ComputationState ): Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = { params.map { nextParam => Seq.from(nextParam.asVar.definedBy.toArray.sorted.map { ds => 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 af070573bb..ae6b6afb28 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 @@ -14,7 +14,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class BinaryExprInterpreter[State <: ComputationState]() extends StringInterpreter[State] { +object BinaryExprInterpreter extends StringInterpreter { override type T = BinaryExpr[V] @@ -26,7 +26,7 @@ case class BinaryExprInterpreter[State <: ComputationState]() extends StringInte * * For all other expressions, a [[StringConstancyInformation.neutralElement]] will be returned. */ - def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { val sci = instr.cTpe match { case ComputationalTypeInt => StringConstancyInformation.dynamicInt case ComputationalTypeFloat => StringConstancyInformation.dynamicFloat @@ -35,10 +35,3 @@ case class BinaryExprInterpreter[State <: ComputationState]() extends StringInte computeFinalResult(pc, sci) } } - -object BinaryExprInterpreter { - - def interpret[State <: ComputationState](instr: BinaryExpr[V], pc: Int)(implicit - state: State - ): ProperPropertyComputationResult = BinaryExprInterpreter[State]().interpret(instr, pc) -} 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 7f57a753f0..996761b5ab 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 @@ -26,12 +26,12 @@ import org.opalj.fpcf.ProperPropertyComputationResult * * @author Maximilian Rüsch */ -abstract class InterpretationHandler[State <: ComputationState] { +abstract class InterpretationHandler { def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = - processDefSitePC(entity.pc)(entity.state.asInstanceOf[State]) + processDefSitePC(entity.pc)(entity.state) - private def processDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + private def processDefSitePC(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { if (pc <= FormalParametersOriginOffset) { if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) @@ -43,7 +43,7 @@ abstract class InterpretationHandler[State <: ComputationState] { processNewDefSitePC(pc) } - protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult + protected def processNewDefSitePC(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult } object InterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index c03c0df388..5d66dfb705 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -14,11 +14,11 @@ import org.opalj.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends StringInterpreter[State] { +object SimpleValueConstExprInterpreter extends StringInterpreter { override type T = SimpleValueConst - def interpret(expr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + def interpret(expr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { val treeOpt = expr match { case ic: IntConst => Some(StringTreeConst(ic.value.toString)) case fc: FloatConst => Some(StringTreeConst(fc.value.toString)) @@ -34,10 +34,3 @@ case class SimpleValueConstExprInterpreter[State <: ComputationState]() extends computeFinalResult(pc, sci) } } - -object SimpleValueConstExprInterpreter { - - def interpret[State <: ComputationState](instr: SimpleValueConst, pc: Int)(implicit - state: State - ): ProperPropertyComputationResult = SimpleValueConstExprInterpreter[State]().interpret(instr, pc) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 6cfb31974c..5b269ed53d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -6,7 +6,6 @@ package analyses package string_analysis package l0 -import org.opalj.br.DefinedMethod import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FinalEP @@ -18,10 +17,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI -trait L0ComputationState extends ComputationState - -trait L0StringInterpreter[State <: L0ComputationState] extends StringInterpreter[State] - /** * 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''. @@ -50,16 +45,9 @@ trait L0StringInterpreter[State <: L0ComputationState] extends StringInterpreter */ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - protected[l0] case class CState( - override val dm: DefinedMethod, - override val entity: SContext - ) extends L0ComputationState - - override type State = CState - override def analyze(data: SContext): ProperPropertyComputationResult = { - val state = CState(declaredMethods(data._2), data) - val iHandler = L0InterpretationHandler[CState]() + val state = ComputationState(declaredMethods(data._2), data) + val iHandler = L0InterpretationHandler() val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { @@ -77,8 +65,8 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } override protected[string_analysis] def determinePossibleStrings(implicit - state: State, - iHandler: InterpretationHandler[State] + state: ComputationState, + iHandler: InterpretationHandler ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac @@ -154,8 +142,6 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis object LazyL0StringAnalysis extends LazyStringAnalysis { - override type State = L0ComputationState - override def init(p: SomeProject, ps: PropertyStore): InitializationData = (new L0StringAnalysis(p), L0InterpretationHandler()(p, ps)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index e8cce5bcb8..be9e7f421c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -22,11 +22,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Maximilian Rüsch */ -case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertyStore) extends L0StringInterpreter[State] { +case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter { override type T = ArrayLoad[V] - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { val defSitePCs = getStoreAndLoadDefSitePCs(instr)(state.tac.stmts) val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) @@ -48,7 +48,7 @@ case class L0ArrayAccessInterpreter[State <: L0ComputationState](ps: PropertySto } private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit - state: State + state: ComputationState ): ProperPropertyComputationResult = { var resultSci = StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].sci diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala index 9e41c2610c..cd5f1c62d0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -25,9 +25,9 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Maximilian Rüsch */ -trait L0FunctionCallInterpreter[State <: L0ComputationState] - extends L0StringInterpreter[State] - with ParameterEvaluatingStringInterpreter[State] { +trait L0FunctionCallInterpreter + extends StringInterpreter + with ParameterEvaluatingStringInterpreter { override type T <: FunctionCall[V] @@ -92,7 +92,7 @@ trait L0FunctionCallInterpreter[State <: L0ComputationState] } protected def interpretArbitraryCallToMethods(implicit - state: State, + state: ComputationState, callState: FunctionCallState ): ProperPropertyComputationResult = { callState.calleeMethods.foreach { m => @@ -125,7 +125,7 @@ trait L0FunctionCallInterpreter[State <: L0ComputationState] } private def tryComputeFinalResult(implicit - state: State, + state: ComputationState, callState: FunctionCallState ): ProperPropertyComputationResult = { if (callState.hasDependees) { @@ -155,7 +155,7 @@ trait L0FunctionCallInterpreter[State <: L0ComputationState] } private def continuation( - state: State, + state: ComputationState, callState: FunctionCallState )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 9de987d7c4..cba90bf440 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -20,13 +20,15 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.SimpleValueCon * * @author Maximilian Rüsch */ -class L0InterpretationHandler[State <: L0ComputationState]()( +class L0InterpretationHandler()( implicit p: SomeProject, ps: PropertyStore -) extends InterpretationHandler[State] { +) extends InterpretationHandler { - override protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override protected def processNewDefSitePC(pc: Int)(implicit + state: ComputationState + ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") @@ -60,7 +62,7 @@ class L0InterpretationHandler[State <: L0ComputationState]()( case ExprStmt(_, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter().interpret(expr, pc) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter().interpret(vmc, pc) + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc, pc) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) case _ => @@ -71,9 +73,9 @@ class L0InterpretationHandler[State <: L0ComputationState]()( object L0InterpretationHandler { - def apply[State <: L0ComputationState]()( + def apply()( implicit p: SomeProject, ps: PropertyStore - ): L0InterpretationHandler[State] = new L0InterpretationHandler[State] + ): L0InterpretationHandler = new L0InterpretationHandler } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index 60b5ae1d2b..8371e99029 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -18,17 +18,13 @@ import org.opalj.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** - * Interprets [[NewArray]] expressions without a call graph. - *

    - * * @author Maximilian Rüsch */ -class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) - extends L0StringInterpreter[State] { +class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { override type T = NewArray[V] - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { if (instr.counts.length != 1) { // Only supports 1-D arrays return computeFinalResult(pc, StringConstancyInformation.lb) @@ -64,7 +60,7 @@ class L0NewArrayInterpreter[State <: L0ComputationState](ps: PropertyStore) } } - private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: State): Result = { + private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: ComputationState): Result = { val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) val sci = if (resultsScis.forall(_.isTheNeutralElement)) { // It might be that there are no results; in such a case, set the string information to the lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 18df3219c8..8f70f37c05 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -18,15 +18,15 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Maximilian Rüsch */ -case class L0NonVirtualFunctionCallInterpreter[State <: L0ComputationState]()( +case class L0NonVirtualFunctionCallInterpreter()( implicit val p: SomeProject, implicit val ps: PropertyStore -) extends L0StringInterpreter[State] - with L0FunctionCallInterpreter[State] { +) extends StringInterpreter + with L0FunctionCallInterpreter { override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { return computeFinalResult(pc, StringConstancyInformation.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 598258b38e..61b9f245ea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -20,19 +20,19 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation /** * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: PropertyStore) - extends L0StringInterpreter[State] { +case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) + extends StringInterpreter { override type T = NonVirtualMethodCall[V] - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr, pc) case _ => computeFinalResult(pc, StringConstancyInformation.neutralElement) } } - private def interpretInit(init: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + private def interpretInit(init: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { init.params.size match { case 0 => computeFinalResult(pc, StringConstancyInformation.neutralElement) case _ => @@ -57,7 +57,7 @@ case class L0NonVirtualMethodCallInterpreter[State <: L0ComputationState](ps: Pr } private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit - state: State + state: ComputationState ): Result = computeFinalResult( pc, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index c22f7d464b..74b7cc1b52 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -23,17 +23,17 @@ import org.opalj.tac.fpcf.properties.TACAI /** * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]()( +case class L0StaticFunctionCallInterpreter()( implicit override val p: SomeProject, override val ps: PropertyStore -) extends L0StringInterpreter[State] - with L0ArbitraryStaticFunctionCallInterpreter[State] - with L0StringValueOfFunctionCallInterpreter[State] { +) extends StringInterpreter + with L0ArbitraryStaticFunctionCallInterpreter + with L0StringValueOfFunctionCallInterpreter { override type T = StaticFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { instr.name match { case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, pc) case _ => interpretArbitraryCall(instr, pc) @@ -41,16 +41,16 @@ case class L0StaticFunctionCallInterpreter[State <: L0ComputationState]()( } } -private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter[State <: L0ComputationState] - extends StringInterpreter[State] - with L0FunctionCallInterpreter[State] { +private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter + extends StringInterpreter + with L0FunctionCallInterpreter { implicit val p: SomeProject override type T = StaticFunctionCall[V] def interpretArbitraryCall(instr: T, pc: Int)(implicit - state: State + state: ComputationState ): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) if (calleeMethod.isEmpty) { @@ -65,14 +65,13 @@ private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter[State <: } } -private[string_analysis] trait L0StringValueOfFunctionCallInterpreter[State <: L0ComputationState] - extends StringInterpreter[State] { +private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends StringInterpreter { override type T <: StaticFunctionCall[V] val ps: PropertyStore - def processStringValueOf(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + def processStringValueOf(call: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { def finalResult(results: Seq[SomeFinalEP]): Result = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 8187d48f5c..234c04905b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -39,12 +39,12 @@ import org.opalj.value.TheIntegerValue * * @author Maximilian Rüsch */ -case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( +case class L0VirtualFunctionCallInterpreter( override val ps: PropertyStore -) extends L0StringInterpreter[State] - with L0ArbitraryVirtualFunctionCallInterpreter[State] - with L0AppendCallInterpreter[State] - with L0SubstringCallInterpreter[State] { +) extends StringInterpreter + with L0ArbitraryVirtualFunctionCallInterpreter + with L0AppendCallInterpreter + with L0SubstringCallInterpreter { override type T = VirtualFunctionCall[V] @@ -68,7 +68,7 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { instr.name match { case "append" => interpretAppendCall(instr, pc) case "toString" => interpretToStringCall(instr, pc) @@ -91,7 +91,9 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( * Processes 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: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + private def interpretToStringCall(call: T, pc: Int)(implicit + state: ComputationState + ): ProperPropertyComputationResult = { def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { eps match { case FinalP(sciP: StringConstancyProperty) => @@ -126,22 +128,22 @@ case class L0VirtualFunctionCallInterpreter[State <: L0ComputationState]( /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(pc: Int)(implicit state: State): ProperPropertyComputationResult = + private def interpretReplaceCall(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = computeFinalResult(pc, InterpretationHandler.getStringConstancyInformationForReplace) } -private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter[State <: L0ComputationState] - extends L0StringInterpreter[State] { +private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { - protected def interpretArbitraryCall(call: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = + protected def interpretArbitraryCall(call: T, pc: Int)(implicit + state: ComputationState + ): ProperPropertyComputationResult = computeFinalResult(pc, StringConstancyInformation.lb) } /** * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. */ -private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationState] - extends L0StringInterpreter[State] { +private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter { override type T = VirtualFunctionCall[V] @@ -177,7 +179,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta } def interpretAppendCall(appendCall: T, pc: Int)(implicit - state: State + state: ComputationState ): ProperPropertyComputationResult = { // Get receiver results val receiverResults = appendCall.receiver.asVar.definedBy.toList.sorted.map { ds => @@ -200,7 +202,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta } private def continuation( - state: State, + state: ComputationState, appendState: AppendCallState )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { @@ -213,7 +215,7 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta } private def tryComputeFinalAppendCallResult(implicit - state: State, + state: ComputationState, appendState: AppendCallState ): ProperPropertyComputationResult = { if (appendState.hasDependees) { @@ -278,15 +280,14 @@ private[string_analysis] trait L0AppendCallInterpreter[State <: L0ComputationSta /** * Interprets calls to [[String#substring]]. */ -private[string_analysis] trait L0SubstringCallInterpreter[State <: L0ComputationState] - extends L0StringInterpreter[State] { +private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpreter { override type T = VirtualFunctionCall[V] val ps: PropertyStore def interpretSubstringCall(substringCall: T, pc: Int)(implicit - state: State + state: ComputationState ): ProperPropertyComputationResult = { val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) @@ -309,7 +310,7 @@ private[string_analysis] trait L0SubstringCallInterpreter[State <: L0Computation private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( results: Seq[SomeFinalEP] - )(implicit state: State): Result = { + )(implicit state: ComputationState): Result = { val receiverSci = StringConstancyInformation.reduceMultiple(results.map { _.p.asInstanceOf[StringConstancyProperty].sci }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index adacae10d2..0503795611 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -14,7 +14,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult /** * @author Maximilian Rüsch */ -case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends L0StringInterpreter[State] { +object L0VirtualMethodCallInterpreter extends StringInterpreter { override type T = VirtualMethodCall[V] @@ -31,7 +31,7 @@ case class L0VirtualMethodCallInterpreter[State <: L0ComputationState]() extends * * For all other calls, a [[StringConstancyInformation.neutralElement]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength case "setLength" => StringConstancyInformation(StringConstancyType.RESET) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 86fe470402..54f36de64f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -6,8 +6,6 @@ package analyses package string_analysis package l1 -import org.opalj.br.DefinedMethod -import org.opalj.br.Method import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey @@ -21,14 +19,9 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l0.L0ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI -trait L1ComputationState extends L0ComputationState - -trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter[State] - /** * 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''. @@ -56,19 +49,12 @@ trait L1StringInterpreter[State <: L1ComputationState] extends StringInterpreter */ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - protected[l1] case class CState( - override val dm: DefinedMethod, - override val entity: (SEntity, Method) - ) extends L1ComputationState - - override type State = CState - protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) override def analyze(data: SContext): ProperPropertyComputationResult = { // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) - val state = CState(declaredMethods(data._2), data) - val iHandler = L1InterpretationHandler[CState](project, ps) + val state = ComputationState(declaredMethods(data._2), data) + val iHandler = L1InterpretationHandler(project, ps) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.isRefinable) { @@ -92,8 +78,8 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. */ override protected[string_analysis] def determinePossibleStrings(implicit - state: State, - iHandler: InterpretationHandler[State] + state: ComputationState, + iHandler: InterpretationHandler ): ProperPropertyComputationResult = { val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) @@ -175,8 +161,6 @@ object L1StringAnalysis { object LazyL1StringAnalysis extends LazyStringAnalysis { - override type State = L1ComputationState - override final def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Callees)) ++ super.uses override final def init(p: SomeProject, ps: PropertyStore): InitializationData = diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 7404a94344..4bfa47fd80 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -35,13 +35,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis * * @author Maximilian Rüsch */ -case class L1FieldReadInterpreter[State <: L1ComputationState]( +case class L1FieldReadInterpreter( ps: PropertyStore, fieldAccessInformation: FieldAccessInformation, project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider -) extends L1StringInterpreter[State] { +) extends StringInterpreter { override type T = FieldRead[V] @@ -96,7 +96,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { @@ -141,7 +141,7 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( } private def tryComputeFinalResult(implicit - state: State, + state: ComputationState, accessState: FieldReadState ): ProperPropertyComputationResult = { if (accessState.hasDependees) { @@ -168,7 +168,10 @@ case class L1FieldReadInterpreter[State <: L1ComputationState]( } } - private def continuation(state: State, accessState: FieldReadState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation( + state: ComputationState, + accessState: FieldReadState + )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index ca8c1f0410..18447edd57 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -33,16 +33,18 @@ import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMe * * @author Maximilian Rüsch */ -class L1InterpretationHandler[State <: L1ComputationState]( +class L1InterpretationHandler( implicit val p: SomeProject, implicit val ps: PropertyStore -) extends InterpretationHandler[State] { +) extends InterpretationHandler { val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processNewDefSitePC(pc: Int)(implicit state: State): ProperPropertyComputationResult = { + override protected def processNewDefSitePC(pc: Int)(implicit + state: ComputationState + ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") @@ -86,7 +88,7 @@ class L1InterpretationHandler[State <: L1ComputationState]( case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter().interpret(vmc, pc) + L0VirtualMethodCallInterpreter.interpret(vmc, pc) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) @@ -101,6 +103,6 @@ object L1InterpretationHandler { def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, FieldAccessInformationKey, ContextProviderKey) - def apply[State <: L1ComputationState](project: SomeProject, ps: PropertyStore): L1InterpretationHandler[State] = - new L1InterpretationHandler[State]()(project, ps) + def apply(project: SomeProject, ps: PropertyStore): L1InterpretationHandler = + new L1InterpretationHandler()(project, ps) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 6d59be9d0f..fd5ceca51c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -34,12 +34,12 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Maximilian Rüsch */ -class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( +class L1VirtualFunctionCallInterpreter( override implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider -) extends L0VirtualFunctionCallInterpreter[State](ps) - with L1StringInterpreter[State] - with L0FunctionCallInterpreter[State] { +) extends L0VirtualFunctionCallInterpreter(ps) + with StringInterpreter + with L0FunctionCallInterpreter { override type T = VirtualFunctionCall[V] @@ -50,7 +50,7 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( ) override protected def interpretArbitraryCall(instr: T, pc: Int)( - implicit state: State + implicit state: ComputationState ): ProperPropertyComputationResult = { val depender = CalleeDepender(pc, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) @@ -77,7 +77,10 @@ class L1VirtualFunctionCallInterpreter[State <: L1ComputationState]( } } - private def continuation(state: State, depender: CalleeDepender)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation( + state: ComputationState, + depender: CalleeDepender + )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(c: Callees) => val methods = getMethodsFromCallees(depender.pc, depender.methodContext, c) 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 3aa3ea6805..1d1ae35530 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 @@ -28,8 +28,8 @@ object PathTransformer { /** * Accumulator function for transforming a path into a StringTree element. */ - private def pathToTreeAcc[State <: ComputationState](subpath: SubPath)(implicit - state: State, + private def pathToTreeAcc(subpath: SubPath)(implicit + state: ComputationState, ps: PropertyStore ): Option[StringTreeNode] = { subpath match { @@ -101,8 +101,8 @@ object PathTransformer { * 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[State <: ComputationState](path: Path)(implicit - state: State, + def pathToStringTree(path: Path)(implicit + state: ComputationState, ps: PropertyStore ): StringTreeNode = { path.elements.size match { From 7f80037475f9b98b8783b51d6e032dbe1e26f337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 14 Mar 2024 00:57:13 +0100 Subject: [PATCH 389/583] Remove unused code --- .../fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 54f36de64f..258325c220 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -9,7 +9,6 @@ package l1 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey -import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.FinalEP @@ -49,8 +48,6 @@ import org.opalj.tac.fpcf.properties.TACAI */ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) - override def analyze(data: SContext): ProperPropertyComputationResult = { // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) val state = ComputationState(declaredMethods(data._2), data) From f06b1e2b53a3f7ee21f4583fb84bd77378a67668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 14 Mar 2024 12:00:11 +0100 Subject: [PATCH 390/583] Use own state for def site analysis --- .../string_analysis/l1/L1TestMethods.java | 21 +++++---- .../string_analysis/ComputationState.scala | 2 + .../string_analysis/EPSDepender.scala | 6 +-- .../string_analysis/StringAnalysis.scala | 3 +- .../string_analysis/StringInterpreter.scala | 25 +++++----- .../BinaryExprInterpreter.scala | 2 +- .../InterpretationHandler.scala | 27 +++++++---- .../SimpleValueConstExprInterpreter.scala | 2 +- .../string_analysis/l0/L0StringAnalysis.scala | 10 +++- .../L0ArrayAccessInterpreter.scala | 10 ++-- .../L0FunctionCallInterpreter.scala | 31 ++++++------- .../L0InterpretationHandler.scala | 6 +-- .../L0NewArrayInterpreter.scala | 12 ++--- .../L0NonVirtualFunctionCallInterpreter.scala | 8 ++-- .../L0NonVirtualMethodCallInterpreter.scala | 15 +++--- .../L0StaticFunctionCallInterpreter.scala | 16 +++---- .../L0VirtualFunctionCallInterpreter.scala | 46 +++++++++---------- .../L0VirtualMethodCallInterpreter.scala | 2 +- .../string_analysis/l1/L1StringAnalysis.scala | 5 +- .../L1FieldReadInterpreter.scala | 23 ++++------ .../L1InterpretationHandler.scala | 4 +- .../L1VirtualFunctionCallInterpreter.scala | 18 ++++---- .../preprocessing/PathTransformer.scala | 5 +- .../string_analysis/string_analysis.scala | 3 +- 24 files changed, 157 insertions(+), 145 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 6ce8bce334..9c31f4ef98 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -299,12 +299,11 @@ public void dependenciesWithinFinalizeTest(String s, Class clazz) { } @StringDefinitionsCollection( - value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " - + "is involved", + value = "a function parameter being analyzed on its own", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "Hello, World" + expectedLevel = DYNAMIC, + expectedStrings = ".*" ) }) @@ -313,12 +312,18 @@ public String callerWithFunctionParameterTest(String s, float i) { return s; } - /** - * Necessary for the callerWithFunctionParameterTest. - */ + @StringDefinitionsCollection( + value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello, World" + ) + + }) public void belongsToSomeTestCase() { String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); - System.out.println(s); + analyzeString(s); } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index a2cd896af7..22dec2e645 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -38,3 +38,5 @@ case class ComputationState(dm: DefinedMethod, entity: SContext) { */ var dependees: List[EOptionP[Entity, Property]] = List() } + +case class DefSiteState(pc: Int, dm: DefinedMethod, tac: TAC) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala index e0479b82bb..7908f40f01 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala @@ -10,14 +10,14 @@ import org.opalj.fpcf.SomeEOptionP /** * @author Maximilian Rüsch */ -private[string_analysis] case class EPSDepender[T <: ASTNode[V], State <: ComputationState]( +private[string_analysis] case class EPSDepender[T <: ASTNode[V]]( instr: T, pc: Int, - state: State, + state: DefSiteState, dependees: Seq[SomeEOptionP] ) { - def withDependees(newDependees: Seq[SomeEOptionP]): EPSDepender[T, State] = EPSDepender( + def withDependees(newDependees: Seq[SomeEOptionP]): EPSDepender[T] = EPSDepender( instr, pc, state, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 879937a99e..95f26bb07c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -151,7 +151,8 @@ trait StringAnalysis extends FPCFAnalysis { var hasFinalResult = true p.elements.foreach { case fpe: FlatPathElement => - val eOptP = ps(InterpretationHandler.getEntityFromDefSitePC(fpe.pc), StringConstancyProperty.key) + val eOptP = + ps(InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac), StringConstancyProperty.key) if (eOptP.isRefinable) { hasFinalResult = false } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index c8dfa76cd4..91687a2ca1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -31,15 +31,15 @@ trait StringInterpreter { * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given * instruction. */ - def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult + def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: ComputationState): Result = + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DefSiteState): Result = StringInterpreter.computeFinalResult(pc, sci) // IMPROVE remove this since awaiting all final is not really feasible // replace with intermediate lattice result approach protected final def awaitAllFinalContinuation( - depender: EPSDepender[T, ComputationState], + depender: EPSDepender[T], finalResult: Seq[SomeFinalEP] => ProperPropertyComputationResult )(result: SomeEPS): ProperPropertyComputationResult = { if (result.isFinal) { @@ -52,7 +52,7 @@ trait StringInterpreter { finalResult(updatedDependees.asInstanceOf[Seq[SomeFinalEP]]) } else { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(depender.pc)(depender.state), + InterpretationHandler.getEntityForPC(depender.pc)(depender.state), StringConstancyProperty.lb, depender.dependees.toSet, awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) @@ -60,7 +60,7 @@ trait StringInterpreter { } } else { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(depender.pc)(depender.state), + InterpretationHandler.getEntityForPC(depender.pc)(depender.state), StringConstancyProperty.lb, depender.dependees.toSet, awaitAllFinalContinuation(depender, finalResult) @@ -71,17 +71,18 @@ trait StringInterpreter { object StringInterpreter { - def computeFinalResult[State <: ComputationState](pc: Int, sci: StringConstancyInformation)(implicit - state: State - ): Result = - Result(FinalEP(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty(sci))) + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DefSiteState): Result = + Result(FinalEP( + InterpretationHandler.getEntityForPC(pc), + StringConstancyProperty(sci.copy(tree = sci.tree.simplify)) + )) } trait ParameterEvaluatingStringInterpreter extends StringInterpreter { val ps: PropertyStore - protected def getParametersForPC(pc: Int)(implicit state: ComputationState): Seq[Expr[V]] = { + protected def getParametersForPC(pc: Int)(implicit state: DefSiteState): Seq[Expr[V]] = { state.tac.stmts(state.tac.pcToIndex(pc)) match { case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params case Assignment(_, _, fc: FunctionCall[V]) => fc.params @@ -90,11 +91,11 @@ trait ParameterEvaluatingStringInterpreter extends StringInterpreter { } protected def evaluateParameters(params: Seq[Expr[V]])(implicit - state: ComputationState + state: DefSiteState ): Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = { params.map { nextParam => Seq.from(nextParam.asVar.definedBy.toArray.sorted.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) }) } } 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 ae6b6afb28..de570a278a 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 @@ -26,7 +26,7 @@ object BinaryExprInterpreter extends StringInterpreter { * * For all other expressions, a [[StringConstancyInformation.neutralElement]] will be returned. */ - def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { val sci = instr.cTpe match { case ComputationalTypeInt => StringConstancyInformation.dynamicInt case ComputationalTypeFloat => StringConstancyInformation.dynamicFloat 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 996761b5ab..8a4eff3454 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 @@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset +import org.opalj.br.DefinedMethod import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyType @@ -28,10 +29,9 @@ import org.opalj.fpcf.ProperPropertyComputationResult */ abstract class InterpretationHandler { - def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = - processDefSitePC(entity.pc)(entity.state) - - private def processDefSitePC(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = { + val pc = entity.pc + implicit val defSiteState: DefSiteState = DefSiteState(pc, entity.dm, entity.tac) if (pc <= FormalParametersOriginOffset) { if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) @@ -43,7 +43,7 @@ abstract class InterpretationHandler { processNewDefSitePC(pc) } - protected def processNewDefSitePC(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult + protected def processNewDefSitePC(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult } object InterpretationHandler { @@ -202,9 +202,18 @@ object InterpretationHandler { def getStringConstancyInformationForReplace: StringConstancyInformation = StringConstancyInformation(StringConstancyType.REPLACE, StringTreeDynamicString) - def getEntityFromDefSite(defSite: Int)(implicit state: ComputationState): DefSiteEntity = - getEntityFromDefSitePC(pcOfDefSite(defSite)(state.tac.stmts)) + def getEntityForDefSite(defSite: Int)(implicit state: DefSiteState): DefSiteEntity = + getEntityForPC(pcOfDefSite(defSite)(state.tac.stmts)) + + def getEntityForDefSite(defSite: Int, dm: DefinedMethod, tac: TAC): DefSiteEntity = + getEntityForPC(pcOfDefSite(defSite)(tac.stmts), dm, tac) + + def getEntityForPC(pc: Int)(implicit state: DefSiteState): DefSiteEntity = + getEntityForPC(pc, state.dm, state.tac) + + def getEntity(state: DefSiteState): DefSiteEntity = + getEntityForPC(state.pc, state.dm, state.tac) - def getEntityFromDefSitePC(defSitePC: Int)(implicit state: ComputationState): DefSiteEntity = - DefSiteEntity(defSitePC, state) + def getEntityForPC(pc: Int, dm: DefinedMethod, tac: TAC): DefSiteEntity = + DefSiteEntity(pc, dm, tac) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index 5d66dfb705..f6e3ca42cb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -18,7 +18,7 @@ object SimpleValueConstExprInterpreter extends StringInterpreter { override type T = SimpleValueConst - def interpret(expr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + def interpret(expr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { val treeOpt = expr match { case ic: IntConst => Some(StringTreeConst(ic.value.toString)) case fc: FloatConst => Some(StringTreeConst(fc.value.toString)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 5b269ed53d..4a274f41b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -89,7 +89,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - val ep = ps(InterpretationHandler.getEntityFromDefSite(defSites.head), StringConstancyProperty.key) + val ep = ps( + InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, state.tac), + StringConstancyProperty.key + ) if (ep.isRefinable) { state.dependees = ep :: state.dependees InterimResult.forLB( @@ -124,7 +127,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis } getPCsInPath(state.computedLeanPath).foreach { pc => - propertyStore(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) match { + propertyStore( + InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), + StringConstancyProperty.key + ) match { case FinalEP(e, _) => state.dependees = state.dependees.filter(_.e != e) case ep => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index be9e7f421c..bb53ac4ec6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -26,15 +26,13 @@ case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter override type T = ArrayLoad[V] - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { val defSitePCs = getStoreAndLoadDefSitePCs(instr)(state.tac.stmts) - val results = defSitePCs.map { pc => - ps(InterpretationHandler.getEntityFromDefSitePC(pc), StringConstancyProperty.key) - } + val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( @@ -48,7 +46,7 @@ case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter } private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { var resultSci = StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].sci diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala index cd5f1c62d0..a1edcf6341 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -34,11 +34,13 @@ trait L0FunctionCallInterpreter implicit val ps: PropertyStore protected[this] case class FunctionCallState( - defSitePC: Int, + state: DefSiteState, calleeMethods: Seq[Method], var tacDependees: Map[Method, EOptionP[Method, TACAI]], var returnDependees: Map[Method, Seq[EOptionP[SContext, StringConstancyProperty]]] = Map.empty ) { + def pc: Int = state.pc + var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) private var _paramDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = Seq.empty @@ -92,7 +94,6 @@ trait L0FunctionCallInterpreter } protected def interpretArbitraryCallToMethods(implicit - state: ComputationState, callState: FunctionCallState ): ProperPropertyComputationResult = { callState.calleeMethods.foreach { m => @@ -124,16 +125,13 @@ trait L0FunctionCallInterpreter tryComputeFinalResult } - private def tryComputeFinalResult(implicit - state: ComputationState, - callState: FunctionCallState - ): ProperPropertyComputationResult = { + private def tryComputeFinalResult(implicit callState: FunctionCallState): ProperPropertyComputationResult = { if (callState.hasDependees) { - InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(callState.defSitePC), - StringConstancyProperty.lb, + InterimResult.forUB( + InterpretationHandler.getEntityForPC(callState.pc)(callState.state), + StringConstancyProperty.ub, callState.dependees.toSet, - continuation(state, callState) + continuation(callState) ) } else { val parameterScis = callState.paramDependees.zipWithIndex.map { @@ -150,27 +148,24 @@ trait L0FunctionCallInterpreter } } - computeFinalResult(callState.defSitePC, StringConstancyInformation.reduceMultiple(methodScis)) + computeFinalResult(callState.pc, StringConstancyInformation.reduceMultiple(methodScis))(callState.state) } } - private def continuation( - state: ComputationState, - callState: FunctionCallState - )(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(callState: FunctionCallState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case EUBP(m: Method, _: TACAI) => callState.tacDependees += m -> eps.asInstanceOf[EOptionP[Method, TACAI]] - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(callState) case EUBP(_: (_, _), _: StringConstancyProperty) => val contextEPS = eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]] callState.updateReturnDependee(contextEPS.e._2, contextEPS) - tryComputeFinalResult(state, callState) + tryComputeFinalResult(callState) case EUBP(_: DefSiteEntity, _: StringConstancyProperty) => callState.updateParamDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) - tryComputeFinalResult(state, callState) + tryComputeFinalResult(callState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index cba90bf440..15effb10c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -26,9 +26,7 @@ class L0InterpretationHandler()( ps: PropertyStore ) extends InterpretationHandler { - override protected def processNewDefSitePC(pc: Int)(implicit - state: ComputationState - ): ProperPropertyComputationResult = { + override protected def processNewDefSitePC(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") @@ -39,7 +37,7 @@ class L0InterpretationHandler()( case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) + case Assignment(_, _, expr: NewArray[V]) => L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index 8371e99029..a82675cc31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -20,11 +20,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation /** * @author Maximilian Rüsch */ -class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { +case class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { override type T = NewArray[V] - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { if (instr.counts.length != 1) { // Only supports 1-D arrays return computeFinalResult(pc, StringConstancyInformation.lb) @@ -36,10 +36,10 @@ class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { val allResults = arrValuesDefSites.flatMap { ds => if (ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[V]]) { state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } } else if (ds < 0) { - Seq(ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key)) + Seq(ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key)) } else { Seq.empty } @@ -47,7 +47,7 @@ class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { if (allResults.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, allResults.filter(_.isRefinable).toSet, awaitAllFinalContinuation( @@ -60,7 +60,7 @@ class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { } } - private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: ComputationState): Result = { + private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: DefSiteState): Result = { val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) val sci = if (resultsScis.forall(_.isTheNeutralElement)) { // It might be that there are no results; in such a case, set the string information to the lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 8f70f37c05..5c6a9cd082 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -26,16 +26,16 @@ case class L0NonVirtualFunctionCallInterpreter()( override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { - val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { return computeFinalResult(pc, StringConstancyInformation.lb) } val m = calleeMethod.value - val callState = FunctionCallState(pc, Seq(m), Map((m, ps(m, TACAI.key)))) + val callState = FunctionCallState(state, Seq(m), Map((m, ps(m, TACAI.key)))) callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(callState) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 61b9f245ea..3abd9577e3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -20,31 +20,30 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation /** * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) - extends StringInterpreter { +case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringInterpreter { override type T = NonVirtualMethodCall[V] - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr, pc) case _ => computeFinalResult(pc, StringConstancyInformation.neutralElement) } } - private def interpretInit(init: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + private def interpretInit(init: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { init.params.size match { case 0 => computeFinalResult(pc, StringConstancyInformation.neutralElement) case _ => // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val results = init.params.head.asVar.definedBy.toList.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } if (results.forall(_.isFinal)) { finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } else { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, results.toSet, awaitAllFinalContinuation( @@ -56,9 +55,7 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) } } - private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit - state: ComputationState - ): Result = + private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit state: DefSiteState): Result = computeFinalResult( pc, StringConstancyInformation.reduceMultiple(results.map { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 74b7cc1b52..3c50b5989c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -33,7 +33,7 @@ case class L0StaticFunctionCallInterpreter()( override type T = StaticFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { instr.name match { case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, pc) case _ => interpretArbitraryCall(instr, pc) @@ -50,18 +50,18 @@ private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter override type T = StaticFunctionCall[V] def interpretArbitraryCall(instr: T, pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { - val calleeMethod = instr.resolveCallTarget(state.entity._2.classFile.thisType) + val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { return computeFinalResult(pc, StringConstancyInformation.lb) } val m = calleeMethod.value - val callState = FunctionCallState(pc, Seq(m), Map((m, ps(m, TACAI.key)))) + val callState = FunctionCallState(state, Seq(m), Map((m, ps(m, TACAI.key)))) callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(callState) } } @@ -71,7 +71,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends St val ps: PropertyStore - def processStringValueOf(call: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + def processStringValueOf(call: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { def finalResult(results: Seq[SomeFinalEP]): Result = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } @@ -91,11 +91,11 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends St } val results = call.params.head.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 234c04905b..4e9ed502f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -68,7 +68,7 @@ case class L0VirtualFunctionCallInterpreter( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { instr.name match { case "append" => interpretAppendCall(instr, pc) case "toString" => interpretToStringCall(instr, pc) @@ -92,7 +92,7 @@ case class L0VirtualFunctionCallInterpreter( * the given `toString` is such a function call! Otherwise, the expected behavior cannot be guaranteed. */ private def interpretToStringCall(call: T, pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { eps match { @@ -101,7 +101,7 @@ case class L0VirtualFunctionCallInterpreter( case iep: InterimEP[_, _] if eps.pk == StringConstancyProperty.key => InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), iep.lb.asInstanceOf[StringConstancyProperty], Set(eps), computeResult @@ -109,7 +109,7 @@ case class L0VirtualFunctionCallInterpreter( case _ if eps.pk == StringConstancyProperty.key => InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, Set(eps), computeResult @@ -120,7 +120,7 @@ case class L0VirtualFunctionCallInterpreter( } computeResult(ps( - InterpretationHandler.getEntityFromDefSite(call.receiver.asVar.definedBy.head), + InterpretationHandler.getEntityForDefSite(call.receiver.asVar.definedBy.head), StringConstancyProperty.key )) } @@ -128,14 +128,14 @@ case class L0VirtualFunctionCallInterpreter( /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = + private def interpretReplaceCall(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = computeFinalResult(pc, InterpretationHandler.getStringConstancyInformationForReplace) } private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { protected def interpretArbitraryCall(call: T, pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = computeFinalResult(pc, StringConstancyInformation.lb) } @@ -153,6 +153,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter appendCall: T, param: V, defSitePC: Int, + state: DefSiteState, var receiverDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]], var valueDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]] ) { @@ -179,11 +180,11 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter } def interpretAppendCall(appendCall: T, pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { // Get receiver results val receiverResults = appendCall.receiver.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } // Get parameter results // .head because we want to evaluate only the first argument of append @@ -194,36 +195,33 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter } else { ds } - ps(InterpretationHandler.getEntityFromDefSite(usedDS), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(usedDS), StringConstancyProperty.key) } - implicit val appendState: AppendCallState = AppendCallState(appendCall, param, pc, receiverResults, valueResults) + implicit val appendState: AppendCallState = + AppendCallState(appendCall, param, pc, state, receiverResults, valueResults) tryComputeFinalAppendCallResult } - private def continuation( - state: ComputationState, - appendState: AppendCallState - )(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(appendState: AppendCallState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => appendState.updateDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) - tryComputeFinalAppendCallResult(state, appendState) + tryComputeFinalAppendCallResult(appendState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } } private def tryComputeFinalAppendCallResult(implicit - state: ComputationState, appendState: AppendCallState ): ProperPropertyComputationResult = { if (appendState.hasDependees) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(appendState.defSitePC), + InterpretationHandler.getEntityForPC(appendState.defSitePC)(appendState.state), StringConstancyProperty.lb, appendState.dependees.toSet, - continuation(state, appendState) + continuation(appendState) ) } else { val receiverSci = StringConstancyInformation.reduceMultiple(appendState.receiverDependees.map { @@ -239,7 +237,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter StringConstancyType.APPEND, StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree) ) - ) + )(appendState.state) } } @@ -287,15 +285,15 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre val ps: PropertyStore def interpretSubstringCall(substringCall: T, pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityFromDefSite(ds), StringConstancyProperty.key) + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } if (receiverResults.exists(_.isRefinable)) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.lb, receiverResults.toSet, awaitAllFinalContinuation( @@ -310,7 +308,7 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( results: Seq[SomeFinalEP] - )(implicit state: ComputationState): Result = { + )(implicit state: DefSiteState): Result = { val receiverSci = StringConstancyInformation.reduceMultiple(results.map { _.p.asInstanceOf[StringConstancyProperty].sci }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 0503795611..4cd110c21e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -31,7 +31,7 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { * * For all other calls, a [[StringConstancyInformation.neutralElement]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength case "setLength" => StringConstancyInformation(StringConstancyType.RESET) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 258325c220..c3adc82acd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -101,7 +101,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - val ep = ps(InterpretationHandler.getEntityFromDefSite(defSites.head), StringConstancyProperty.key) + val ep = ps( + InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, state.tac), + StringConstancyProperty.key + ) if (ep.isRefinable) { state.dependees = ep :: state.dependees InterimResult.forLB( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 4bfa47fd80..b601220965 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -72,6 +72,7 @@ case class L1FieldReadInterpreter( private case class FieldReadState( defSitePC: Int, + state: DefSiteState, var hasInit: Boolean = false, var hasUnresolvableAccess: Boolean = false, var accessDependees: Seq[EOptionP[SContext, StringConstancyProperty]] = Seq.empty @@ -96,7 +97,7 @@ case class L1FieldReadInterpreter( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, pc: Int)(implicit state: ComputationState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { @@ -119,7 +120,7 @@ case class L1FieldReadInterpreter( ) } - implicit val accessState: FieldReadState = FieldReadState(pc) + implicit val accessState: FieldReadState = FieldReadState(pc, state) writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod @@ -140,16 +141,13 @@ case class L1FieldReadInterpreter( tryComputeFinalResult } - private def tryComputeFinalResult(implicit - state: ComputationState, - accessState: FieldReadState - ): ProperPropertyComputationResult = { + private def tryComputeFinalResult(implicit accessState: FieldReadState): ProperPropertyComputationResult = { if (accessState.hasDependees) { InterimResult.forLB( - InterpretationHandler.getEntityFromDefSitePC(accessState.defSitePC), + InterpretationHandler.getEntityForPC(accessState.defSitePC)(accessState.state), StringConstancyProperty.lb, accessState.dependees.toSet, - continuation(state, accessState) + continuation(accessState) ) } else { var scis = accessState.accessDependees.map(_.asFinal.p.sci) @@ -164,18 +162,15 @@ case class L1FieldReadInterpreter( scis = scis :+ StringConstancyInformation.lb } - computeFinalResult(accessState.defSitePC, StringConstancyInformation.reduceMultiple(scis)) + computeFinalResult(accessState.defSitePC, StringConstancyInformation.reduceMultiple(scis))(accessState.state) } } - private def continuation( - state: ComputationState, - accessState: FieldReadState - )(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(accessState: FieldReadState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) - tryComputeFinalResult(state, accessState) + tryComputeFinalResult(accessState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 18447edd57..9a6a46b62c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -43,7 +43,7 @@ class L1InterpretationHandler( implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) override protected def processNewDefSitePC(pc: Int)(implicit - state: ComputationState + state: DefSiteState ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { @@ -54,7 +54,7 @@ class L1InterpretationHandler( case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, expr: NewArray[V]) => new L0NewArrayInterpreter(ps).interpret(expr, pc) + case Assignment(_, _, expr: NewArray[V]) => L0NewArrayInterpreter(ps).interpret(expr, pc) case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index fd5ceca51c..68fe7deaa2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -50,7 +50,7 @@ class L1VirtualFunctionCallInterpreter( ) override protected def interpretArbitraryCall(instr: T, pc: Int)( - implicit state: ComputationState + implicit state: DefSiteState ): ProperPropertyComputationResult = { val depender = CalleeDepender(pc, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) @@ -61,15 +61,15 @@ class L1VirtualFunctionCallInterpreter( computeFinalResult(pc, StringConstancyInformation.lb) } else { val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val callState = FunctionCallState(pc, tacDependees.keys.toSeq, tacDependees) + val callState = FunctionCallState(state, tacDependees.keys.toSeq, tacDependees) callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(callState) } case _ => InterimResult.forUB( - InterpretationHandler.getEntityFromDefSitePC(pc), + InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.ub, Set(depender.calleeDependee), continuation(state, depender) @@ -78,26 +78,26 @@ class L1VirtualFunctionCallInterpreter( } private def continuation( - state: ComputationState, + state: DefSiteState, depender: CalleeDepender )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(c: Callees) => - val methods = getMethodsFromCallees(depender.pc, depender.methodContext, c) + val methods = getMethodsFromCallees(state.pc, depender.methodContext, c) if (methods.isEmpty) { computeFinalResult(depender.pc, StringConstancyInformation.lb)(state) } else { val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val callState = FunctionCallState(depender.pc, tacDependees.keys.toSeq, tacDependees) + val callState = FunctionCallState(state, tacDependees.keys.toSeq, tacDependees) callState.setParamDependees(evaluateParameters(getParametersForPC(depender.pc)(state))(state)) - interpretArbitraryCallToMethods(state, callState) + interpretArbitraryCallToMethods(callState) } case UBP(_: Callees) => depender.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] InterimResult.forUB( - InterpretationHandler.getEntityFromDefSitePC(depender.pc)(state), + InterpretationHandler.getEntity(state), StringConstancyProperty.ub, Set(depender.calleeDependee), continuation(state, depender) 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 1d1ae35530..7699518ba4 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 @@ -34,7 +34,10 @@ object PathTransformer { ): Option[StringTreeNode] = { subpath match { case fpe: FlatPathElement => - val sci = ps(InterpretationHandler.getEntityFromDefSitePC(fpe.pc), StringConstancyProperty.key) match { + val sci = ps( + InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac), + StringConstancyProperty.key + ) match { case FinalP(scp) => scp.sci case _ => StringConstancyInformation.lb } 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 fa441a066e..b5d9dfe26e 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,6 +4,7 @@ package tac package fpcf package analyses +import org.opalj.br.DefinedMethod import org.opalj.br.Method /** @@ -29,5 +30,5 @@ package object string_analysis { * The entity used for requesting string constancy information for specific def sites of an entity. The def site * should be given as a [[org.opalj.br.PC]]. */ - case class DefSiteEntity(pc: Int, state: ComputationState) + case class DefSiteEntity(pc: Int, dm: DefinedMethod, tac: TAC) } From bd9ce1b06759f50a19bfe13d8efb3c12937469d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 15 Mar 2024 12:16:53 +0100 Subject: [PATCH 391/583] Simplify string tree repetition --- .../string_definition/StringTreeNode.scala | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala index 2e4f310ad0..f21a7176db 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala @@ -28,34 +28,24 @@ sealed trait StringTreeNode { def isNeutralElement: Boolean = false } -case class StringTreeRepetition( - child: StringTreeNode, - lowerBound: Option[Int] = None, - upperBound: Option[Int] = None -) extends StringTreeNode { +case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { override val children: Seq[StringTreeNode] = Seq(child) - override def toRegex: String = { - (lowerBound, upperBound) match { - case (Some(lb), Some(ub)) => s"(${child.toRegex}){$lb,$ub}" - case (Some(lb), None) => s"(${child.toRegex}){$lb,}" - case _ => s"(${child.toRegex})*" - } - } + override def toRegex: String = s"(${child.toRegex})*" override def simplify: StringTreeNode = { val simplifiedChild = child.simplify if (simplifiedChild.isNeutralElement) StringTreeNeutralElement else - this.copy(child = simplifiedChild) + StringTreeRepetition(simplifiedChild) } override def constancyLevel: StringConstancyLevel.Value = child.constancyLevel def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = - this.copy(child = child.replaceParameters(parameters)) + StringTreeRepetition(child.replaceParameters(parameters)) } case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends StringTreeNode { From 62c88db685564e9dd79136cf856e3694569bc7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 15 Mar 2024 12:18:19 +0100 Subject: [PATCH 392/583] Remove unwanted changes --- .../scala/org/opalj/br/fpcf/properties/EscapeProperty.scala | 6 ++---- .../org/opalj/br/fpcf/properties/ReturnValueFreshness.scala | 1 + .../main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala | 1 - 3 files changed, 3 insertions(+), 5 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 b901b56b29..1933453e8c 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 @@ -186,10 +186,8 @@ 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)) + } /** 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 6cd6bbfc31..05e3a46903 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 @@ -45,6 +45,7 @@ object ReturnValueFreshness extends ReturnValueFreshnessPropertyMetaInformation // fallback value NoFreshReturnValue ) + } /** 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 index 91c7822468..6a2932edd4 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -361,7 +361,6 @@ class PKECPropertyStore( ): Unit = { val SomeEPS(e, pk) = interimEP var isFresh = false - val ePKState = ps(pk.id).computeIfAbsent(e, { _ => isFresh = true; EPKState(interimEP, c, dependees) }) if (isFresh) { From 7b34f1b7ca95b2c93716b27f6134f5e6b187d02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 2 Apr 2024 21:35:25 +0200 Subject: [PATCH 393/583] Introduce realistic test assertion values --- .../string_analysis/StringConstancyLevel.java | 5 +- .../string_analysis/StringDefinitions.java | 9 +- .../StringDefinitionsCollection.java | 3 +- .../StringAnalysisMatcher.scala | 93 ++++++++++--------- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java index 8b3ac41cce..f4b7dda0fd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java @@ -12,7 +12,9 @@ public enum StringConstancyLevel { // For details, see {@link org.opalj.fpcf.properties.StringConstancyLevel}. CONSTANT("constant"), PARTIALLY_CONSTANT("partially_constant"), - DYNAMIC("dynamic"); + DYNAMIC("dynamic"), + // Added to enable leaving a string constancy level of a string definition unspecified + UNSPECIFIED("unspecified"); private final String value; @@ -23,5 +25,4 @@ public enum StringConstancyLevel { public String getValue() { return value; } - } 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 41d581f17a..bd7cae5dfb 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 @@ -9,7 +9,7 @@ /** * 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. + * whether the string contains only constant 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 +17,7 @@ * 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 + * @author Maximilian Rüsch */ @PropertyValidator(key = "StringConstancy", validator = StringAnalysisMatcher.class) @Documented @@ -25,11 +25,14 @@ @Target({ ElementType.ANNOTATION_TYPE }) public @interface StringDefinitions { + String NO_STRINGS = "N/A"; + /** * This value determines the expected level of freedom for a local variable to * be changed. */ StringConstancyLevel expectedLevel(); + StringConstancyLevel realisticLevel() default StringConstancyLevel.UNSPECIFIED; /** * A regexp like string that describes the element(s) that are expected. For the rules, refer to @@ -38,5 +41,5 @@ * or 2) a five time concatenation of "hello" and/or "world". */ String expectedStrings(); - + String realisticStrings() default NO_STRINGS; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java index deadbc9349..72d3cf307d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java @@ -8,7 +8,7 @@ * expected. This annotation is a wrapper for these expected results. For further information see * {@link StringDefinitions}. * - * @author Patrick Mell + * @author Maximilian Rüsch */ @Documented @Retention(RetentionPolicy.CLASS) @@ -24,5 +24,4 @@ * The expected results in the correct order. */ StringDefinitions[] stringDefinitions(); - } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index 9f4d9dde5e..be99fbe38a 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -10,44 +10,23 @@ import org.opalj.br.analyses.Project import org.opalj.br.fpcf.properties.StringConstancyProperty /** - * 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. - * - * @author Patrick Mell + * @author Maximilian Rüsch */ class StringAnalysisMatcher extends AbstractPropertyMatcher { - /** - * @param a An annotation like of type - * [[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). - */ - 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" - ) + private def getLevelValue(a: AnnotationLike, valueName: String, optional: Boolean): StringConstancyLevel = { + a.elementValuePairs.find(_.name == valueName) match { + case Some(el) => StringConstancyLevel.valueOf(el.value.asEnumValue.constName) + case None if !optional => throw new IllegalArgumentException(s"Could not find $valueName in annotation $a") + case None => StringConstancyLevel.UNSPECIFIED } } - /** - * @param a An annotation like of type - * [[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). - */ - private def getExpectedStrings(a: AnnotationLike): String = { - a.elementValuePairs.find(_.name == "expectedStrings") match { - case Some(el) => el.value.asStringValue.value - case None => throw new IllegalArgumentException( - "Can only extract the possible strings from a StringDefinitions annotation" - ) + private def getStringsValue(a: AnnotationLike, valueName: String, optional: Boolean): String = { + a.elementValuePairs.find(_.name == valueName) match { + case Some(el) => el.value.asStringValue.value + case None if !optional => throw new IllegalArgumentException(s"Could not find $valueName in annotation $a") + case None => StringDefinitions.NO_STRINGS } } @@ -61,25 +40,47 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { a: AnnotationLike, properties: Iterable[Property] ): Option[String] = { - var actLevel = "" - var actString = "" - properties.head match { - case prop: StringConstancyProperty => - val sci = prop.stringConstancyInformation - actLevel = sci.constancyLevel.toString.toLowerCase - actString = sci.tree.toRegex - case _ => + if ( + a.annotationType.asObjectType != ObjectType("org/opalj/fpcf/properties/string_analysis/StringDefinitions") + ) { + throw new IllegalArgumentException( + "Can only extract the constancy level from a @StringDefinitions annotation" + ) } - val expLevel = getConstancyLevel(a).toLowerCase - val expStrings = getExpectedStrings(a) - val errorMsg = s"Level: $expLevel, Strings: $expStrings" + val realisticLevel = getLevelValue(a, "realisticLevel", optional = true) + val realisticStrings = getStringsValue(a, "realisticStrings", optional = true) + val expectedLevel = getLevelValue(a, "expectedLevel", optional = false) + val expectedStrings = getStringsValue(a, "expectedStrings", optional = false) - if (expLevel != actLevel || expStrings != actString) { - return Some(errorMsg) + if (realisticLevel == expectedLevel && realisticStrings == expectedStrings) { + throw new IllegalStateException("Invalid test definition: Realistic and expected values are equal") + } else if ( + realisticLevel == StringConstancyLevel.UNSPECIFIED ^ realisticStrings == StringDefinitions.NO_STRINGS + ) { + throw new IllegalStateException( + "Invalid test definition: Realistic values must either be fully specified or be absent" + ) } - None + val testRealisticValues = + realisticLevel != StringConstancyLevel.UNSPECIFIED && realisticStrings != StringDefinitions.NO_STRINGS + val (testedLevel, testedStrings) = + if (testRealisticValues) (realisticLevel.toString.toLowerCase, realisticStrings) + else (expectedLevel.toString.toLowerCase, expectedStrings) + + val (actLevel, actString) = properties.head match { + case prop: StringConstancyProperty => + val sci = prop.stringConstancyInformation + (sci.constancyLevel.toString.toLowerCase, sci.tree.toRegex) + case _ => ("", "") + } + + if (testedLevel != actLevel || testedStrings != actString) { + Some(s"Level: $testedLevel, Strings: $testedStrings") + } else { + None + } } } From 6168ffdd5e0f0409741cd9a56ac87ba8bb1b0568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 2 Apr 2024 22:47:44 +0200 Subject: [PATCH 394/583] Remove complex path finding for control structures --- .../string_analysis/l0/L0TestMethods.java | 298 ++-- .../string_analysis/l1/L1TestMethods.java | 190 +-- .../org/opalj/fpcf/StringAnalysisTest.scala | 19 +- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 113 -- .../org/opalj/br/cfg/AbstractCFGTest.scala | 16 +- .../org/opalj/br/cfg/DominatorTreeTest.scala | 119 -- .../org/opalj/graphs/DominatorTree.scala | 43 - .../org/opalj/graphs/PostDominatorTree.scala | 23 - .../string_analysis/StringAnalysis.scala | 6 +- .../string_analysis/l0/L0StringAnalysis.scala | 24 +- .../string_analysis/l1/L1StringAnalysis.scala | 46 +- .../preprocessing/AbstractPathFinder.scala | 1312 ----------------- .../preprocessing/DefaultPathFinder.scala | 48 - .../string_analysis/preprocessing/Path.scala | 46 +- .../preprocessing/PathTransformer.scala | 21 +- .../preprocessing/SimplePathFinder.scala | 32 + .../preprocessing/WindowPathFinder.scala | 69 - 17 files changed, 282 insertions(+), 2143 deletions(-) delete mode 100644 OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 54eeac4dee..7559183ecc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -77,12 +77,8 @@ public void analyzeString(String s) { @StringDefinitionsCollection( value = "read-only string variable, trivial case", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.String" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.String" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") } ) public void constantStringReads() { @@ -95,12 +91,8 @@ public void constantStringReads() { @StringDefinitionsCollection( value = "checks if a string value with append(s) is determined correctly", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.String" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.Object" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Object") } ) public void simpleStringConcat() { @@ -120,12 +112,8 @@ public void simpleStringConcat() { @StringDefinitionsCollection( value = "checks if the substring of a constant string value is determined correctly", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "va." - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "va.lang." - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "va."), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "va.lang.") } ) public void simpleSubstring() { @@ -137,12 +125,8 @@ public void simpleSubstring() { @StringDefinitionsCollection( value = "checks if a string value with append(s) is determined correctly", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.String" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.Object" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Object") } ) public void simpleStringConcatWithStaticFunctionCalls() { @@ -153,9 +137,7 @@ public void simpleStringConcatWithStaticFunctionCalls() { @StringDefinitionsCollection( value = "checks if a string value with > 2 continuous appends is determined correctly", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.String" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") }) public void directAppendConcats() { StringBuilder sb = new StringBuilder("java"); @@ -166,9 +148,7 @@ public void directAppendConcats() { @StringDefinitionsCollection( value = "at this point, function call cannot be handled => DYNAMIC", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" - ) + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public void fromFunctionCall() { String className = getStringBuilderClassName(); @@ -178,9 +158,7 @@ public void fromFunctionCall() { @StringDefinitionsCollection( value = "constant string + string from function call => PARTIALLY_CONSTANT", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang..*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang..*") }) public void fromConstantAndFunctionCall() { String className = "java.lang."; @@ -193,8 +171,10 @@ public void fromConstantAndFunctionCall() { value = "array access with unknown index", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(java.lang.String|" - + "java.lang.StringBuilder|java.lang.System|java.lang.Runnable)" + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void fromStringArray(int index) { @@ -214,7 +194,6 @@ public void fromStringArray(int index) { expectedLevel = DYNAMIC, expectedStrings = "(java.lang.Object|.*|java.lang.Integer)" ) - }) public void arrayStaticAndVirtualFunctionCalls(int i) { String[] classes = { @@ -231,7 +210,9 @@ public void arrayStaticAndVirtualFunctionCalls(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(java.lang.System|java.lang.Runtime)" + expectedStrings = "(java.lang.System|java.lang.Runtime)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void multipleConstantDefSites(boolean cond) { @@ -250,7 +231,9 @@ public void multipleConstantDefSites(boolean cond) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "((java.lang.Object|.*)|.*|java.lang.System|java.lang..*)" + expectedStrings = "((java.lang.Object|.*)|.*|java.lang.System|java.lang..*)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void multipleDefSites(int value) { @@ -281,7 +264,8 @@ public void multipleDefSites(int value) { value = "Switch statement with multiple relevant and multiple irrelevant cases", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)?" + expectedLevel = CONSTANT, expectedStrings = "a(b|c)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchRelevantAndIrrelevant(int value) { @@ -305,7 +289,8 @@ public void switchRelevantAndIrrelevant(int value) { value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)?" + expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { @@ -332,7 +317,8 @@ public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { value = "Switch statement with multiple relevant and multiple irrelevant cases and an irrelevant default case", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)?" + expectedLevel = CONSTANT, expectedStrings = "a(b|c)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { @@ -358,7 +344,8 @@ public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { value = "Switch statement with multiple relevant and no irrelevant cases and a relevant default case", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)" + expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchRelevantWithRelevantDefault(int value) { @@ -381,7 +368,8 @@ public void switchRelevantWithRelevantDefault(int value) { value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d)?|f)" + expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d)?|f)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchNestedNoNestedDefault(int value, int value2) { @@ -411,7 +399,8 @@ public void switchNestedNoNestedDefault(int value, int value2) { value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d|e)|f)" + expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d|e)|f)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void switchNestedWithNestedDefault(int value, int value2) { @@ -444,10 +433,12 @@ public void switchNestedWithNestedDefault(int value, int value2) { value = "if-else control structure which append to a string builder with an int expr and an int", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)" + expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(42-42|x)" + expectedLevel = CONSTANT, expectedStrings = "(42-42|x)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifElseWithStringBuilderWithIntExpr() { @@ -467,12 +458,13 @@ public void ifElseWithStringBuilderWithIntExpr() { } @StringDefinitionsCollection( - value = "if-else control structure which append float and double values to a string " - + "builder", + 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" + expectedStrings = "(3.14|^-?\\d*\\.{0,1}\\d+$)2.71828", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void ifElseWithStringBuilderWithFloatExpr() { @@ -492,10 +484,12 @@ public void ifElseWithStringBuilderWithFloatExpr() { value = "if-else control structure which append to a string builder", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(a|b)" + expectedLevel = CONSTANT, expectedStrings = "(a|b)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)" + expectedLevel = CONSTANT, expectedStrings = "a(b|c)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifElseWithStringBuilder1() { @@ -518,7 +512,8 @@ public void ifElseWithStringBuilder1() { value = "if-else control structure which append to a string builder multiple times", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)" + expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifElseWithStringBuilder3() { @@ -540,7 +535,8 @@ public void ifElseWithStringBuilder3() { value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)?" + expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifElseWithStringBuilder4() { @@ -565,10 +561,12 @@ public void ifElseWithStringBuilder4() { stringDefinitions = { // Currently, the analysis does not support determining loop ranges => a(b)* @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*" + expectedLevel = CONSTANT, expectedStrings = "a(b)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*" + expectedLevel = CONSTANT, expectedStrings = "a(b)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void simpleForLoopWithKnownBounds() { @@ -591,7 +589,9 @@ public void simpleForLoopWithKnownBounds() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|^-?\\d+$))*yz" + expectedStrings = "((x|^-?\\d+$))*yz", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void ifElseInLoopWithAppendAfterwards() { @@ -612,7 +612,8 @@ public void ifElseInLoopWithAppendAfterwards() { value = "if control structure without an else", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)?" + expectedLevel = CONSTANT, expectedStrings = "a(b)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifWithoutElse() { @@ -629,7 +630,8 @@ public void ifWithoutElse() { + "that is later read", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*" + expectedLevel = CONSTANT, expectedStrings = "a(b)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void nestedLoops(int range) { @@ -646,8 +648,8 @@ public void nestedLoops(int range) { value = "some example that makes use of a StringBuffer instead of a StringBuilder", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|^-?\\d+$))*yz" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "((x|^-?\\d+$))*yz", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void stringBufferExample() { @@ -668,7 +670,8 @@ public void stringBufferExample() { value = "while-true example", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*" + expectedLevel = CONSTANT, expectedStrings = "a(b)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void whileWithBreak() { @@ -686,7 +689,8 @@ public void whileWithBreak() { value = "an example with a non-while-true loop containing a break", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*" + expectedLevel = CONSTANT, expectedStrings = "a(b)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void whileWithBreak(int i) { @@ -706,11 +710,14 @@ public void whileWithBreak(int i) { value = "an extensive example with many control structures", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): " + expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(iv1|iv2): ((great!)?)*(.*)?" + expectedStrings = "(iv1|iv2): ((great!)?)*(.*)?", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void extensive(boolean cond) { @@ -742,9 +749,7 @@ public void extensive(boolean cond) { @StringDefinitionsCollection( value = "an example with a throw (and no try-catch-finally)", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*") }) public void withThrow(String filename) throws IOException { StringBuilder sb = new StringBuilder("File Content:"); @@ -755,21 +760,20 @@ public void withThrow(String filename) throws IOException { @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 + // Multiple string definition values are necessary because the three-address code contains multiple calls to + // 'analyzeString' which are currently not filtered out stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(.*)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(.*)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(.*)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void withException(String filename) { @@ -787,13 +791,16 @@ public void withException(String filename) { value = "case with a try-catch-finally exception", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void tryCatchFinally(String filename) { @@ -812,17 +819,16 @@ public void tryCatchFinally(String filename) { value = "case with a try-catch-finally throwable", stringDefinitions = { @StringDefinitions( - // 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:(.*|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void tryCatchFinallyWithThrowable(String filename) { @@ -840,12 +846,8 @@ public void tryCatchFinallyWithThrowable(String filename) { @StringDefinitionsCollection( value = "simple examples to clear a StringBuilder", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") }) public void simpleClearExamples() { StringBuilder sb1 = new StringBuilder("init_value:"); @@ -866,7 +868,9 @@ public void simpleClearExamples() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" + expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void advancedClearExampleWithSetLength(int value) { @@ -883,12 +887,12 @@ public void advancedClearExampleWithSetLength(int value) { @StringDefinitionsCollection( value = "a simple and a little more advanced example with a StringBuilder#replace call", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" - ), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|.*Goodbye)" + expectedStrings = "(init_value:Hello, world!Goodbye|.*Goodbye)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void replaceExamples(int value) { @@ -910,15 +914,17 @@ public void replaceExamples(int value) { value = "loops that use breaks and continues (or both)", stringDefinitions = { @StringDefinitions( - // The bytecode produces an "if" within an "if" inside the first loop, - // => two conds - expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" + // The bytecode produces an "if" within an "if" inside the first loop => two conditions + expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "" + expectedLevel = CONSTANT, expectedStrings = "", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*" + expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void breakContinueExamples(int value) { @@ -957,14 +963,13 @@ public void breakContinueExamples(int value) { analyzeString(sb3.toString()); } - - @StringDefinitionsCollection( value = "loops that use breaks and continues (or both)", stringDefinitions = { @StringDefinitions( // The bytecode produces an "if" within an "if" inside the first loop, => two conditions - expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" + expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void breakContinueExamples2(int value) { @@ -982,11 +987,11 @@ public void breakContinueExamples2(int value) { } @StringDefinitionsCollection( - value = "an example where in the condition of an 'if', a string is appended to a " - + "StringBuilder", + value = "an example where in the condition of an 'if', a string is appended to a StringBuilder", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime" + expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void ifConditionAppendsToString(String className) { @@ -1001,12 +1006,8 @@ public void ifConditionAppendsToString(String className) { value = "checks if a string value with > 2 continuous appends and a second " + "StringBuilder is determined correctly", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "B." - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.langStringB." - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "B."), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.langStringB.") }) public void directAppendConcatsWith2ndStringBuilder() { StringBuilder sb = new StringBuilder("java"); @@ -1024,7 +1025,8 @@ public void directAppendConcatsWith2ndStringBuilder() { + "complex construction of a second StringBuilder is determined correctly.", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" + expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void complexSecondStringBuilderRead(String className) { @@ -1048,7 +1050,8 @@ public void complexSecondStringBuilderRead(String className) { + "simple construction of a second StringBuilder is determined correctly.", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" + expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void simpleSecondStringBuilderRead(String className) { @@ -1068,18 +1071,9 @@ public void simpleSecondStringBuilderRead(String className) { @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 = DYNAMIC, - expectedStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "c"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "42.3"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public void valueOfTest() { analyzeString(String.valueOf('c')); @@ -1090,9 +1084,7 @@ public void valueOfTest() { @StringDefinitionsCollection( value = "an example that uses a non final field", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:.*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:.*") }) public void nonFinalFieldRead() { StringBuilder sb = new StringBuilder("Field Value:"); @@ -1105,9 +1097,7 @@ public void nonFinalFieldRead() { value = "an example that reads a public final static field; for these, the string " + "information are available (at least on modern compilers)", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "Field Value:mine" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Field Value:mine") }) public void finalFieldRead() { StringBuilder sb = new StringBuilder("Field Value:"); @@ -1120,10 +1110,12 @@ public void finalFieldRead() { value = "A case with a criss-cross append on two StringBuilders", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?" + expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?" + expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) }) public void crissCrossExample(String className) { @@ -1143,18 +1135,10 @@ public void crissCrossExample(String className) { @StringDefinitionsCollection( value = "examples that use a passed parameter to define strings that are analyzed", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" - ), - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*.*" - ) + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*.*") }) public void parameterRead(String stringValue, StringBuilder sbValue) { analyzeString(stringValue); @@ -1172,9 +1156,7 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { @StringDefinitionsCollection( value = "examples that use a passed parameter to define strings that are analyzed", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*") }) public void parameterRead2(String stringValue, StringBuilder sbValue) { StringBuilder sb = new StringBuilder("value="); @@ -1189,8 +1171,8 @@ public void parameterRead2(String stringValue, StringBuilder sbValue) { + "definition sites and one usage site", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(set.*|s.*)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(set.*|s.*)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), }) public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { @@ -1216,7 +1198,9 @@ public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "Hello: (.*|.*|.*)?" + expectedStrings = "Hello: (.*|.*|.*)?", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ), }) protected void setDebugFlags(String[] var1) { @@ -1279,10 +1263,7 @@ public void unknownCharValue() { @StringDefinitionsCollection( value = "a case where a static method with a string parameter is called", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "java.lang.Integer" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Integer") }) public void fromStaticMethodWithParamTest() { analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); @@ -1299,5 +1280,4 @@ private String getStringBuilderClassName() { private String getSimpleStringBuilderClassName() { return "StringBuilder"; } - } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 9c31f4ef98..abd2be0978 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -4,7 +4,6 @@ import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.HelloGreeting; import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; -import org.opalj.fpcf.fixtures.string_analysis.l0.StringProvider; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -67,6 +66,20 @@ public L1TestMethods(float e) { ) }) public void simpleNonVirtualFunctionCallTest(int i) { + analyzeString(getRuntimeClassName()); + } + + @StringDefinitionsCollection( + value = "a case where a non-virtual function call inside an if statement is interpreted", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" + ) + }) + public void simpleNonVirtualFunctionCallTestWithIf(int i) { String s; if (i == 0) { s = getRuntimeClassName(); @@ -84,7 +97,9 @@ public void simpleNonVirtualFunctionCallTest(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)" + expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void initFromNonVirtualFunctionCallTest(int i) { @@ -104,13 +119,9 @@ public void initFromNonVirtualFunctionCallTest(int i) { value = "a case where a static method is called that returns a string but are not " + "within this project => cannot / will not be interpret", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = ".*" - ), - + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), }) - public void staticMethodOutOfScopeTest() throws FileNotFoundException { + public void staticMethodOutOfScopeTest() { analyzeString(System.getProperty("os.version")); } @@ -120,9 +131,10 @@ public void staticMethodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(.*)*" + expectedStrings = "(.*)*", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) - }) public void methodOutOfScopeTest() throws FileNotFoundException { File file = new File("my-file.txt"); @@ -134,27 +146,6 @@ public void methodOutOfScopeTest() throws FileNotFoundException { analyzeString(sb.toString()); } - @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 = { @@ -162,9 +153,8 @@ public void appendTest0(int i) { expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "classname:StringBuilder,osname:.*" ) - }) - public void appendTest1() { + public void appendTest() { StringBuilder sb = new StringBuilder("classname:"); sb.append(getSimpleStringBuilderClassName()); sb.append(",osname:"); @@ -172,37 +162,10 @@ public void appendTest1() { 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()); - } - @StringDefinitionsCollection( value = "a case where the concrete instance of an interface is known", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "Hello World" - ) - + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Hello World") }) public void knownHierarchyInstanceTest() { GreetingService gs = new HelloGreeting(); @@ -212,25 +175,16 @@ public void knownHierarchyInstanceTest() { @StringDefinitionsCollection( value = "a case where the concrete instance of an interface is NOT known", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(Hello World|Hello)" - ) - + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Hello World|Hello)") }) public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); } @StringDefinitionsCollection( - value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " - + "is involved", + value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = ".*Impl_Stub" - ) - + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = ".*Impl_Stub") }) public void getStaticTest() { analyzeString(rmiServerImplStubClassName); @@ -240,10 +194,9 @@ public void getStaticTest() { value = "a case where the append value has more than one def site", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "It is (great|not great)" + expectedLevel = CONSTANT, expectedStrings = "It is (great|not great)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) - }) public void appendWithTwoDefSites(int i) { String s; @@ -256,14 +209,12 @@ public void appendWithTwoDefSites(int i) { } @StringDefinitionsCollection( - value = "a case where the append value has more than one def site with a function " - + "call involved", + 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)" + expectedLevel = CONSTANT, expectedStrings = "It is (great|Hello, World)", + realisticLevel = DYNAMIC, realisticStrings = ".*" ) - }) public void appendWithTwoDefSitesWithFuncCallTest(int i) { String s; @@ -281,9 +232,10 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "get(.*|Hello, Worldjava.lang.Runtime)" + expectedStrings = "get(.*|Hello, Worldjava.lang.Runtime)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) - }) public void dependenciesWithinFinalizeTest(String s, Class clazz) { String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : @@ -301,11 +253,7 @@ public void dependenciesWithinFinalizeTest(String s, Class clazz) { @StringDefinitionsCollection( value = "a function parameter being analyzed on its own", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = ".*" - ) - + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public String callerWithFunctionParameterTest(String s, float i) { analyzeString(s); @@ -315,11 +263,7 @@ public String callerWithFunctionParameterTest(String s, float i) { @StringDefinitionsCollection( value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "Hello, World" - ) - + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Hello, World") }) public void belongsToSomeTestCase() { String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); @@ -336,14 +280,8 @@ public static String belongsToTheSameTestCase() { @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?" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Hello, World!"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Hello, World?") }) public void functionWithFunctionParameter() { analyzeString(addExclamationMark(getHelloWorld())); @@ -353,10 +291,7 @@ public void functionWithFunctionParameter() { @StringDefinitionsCollection( value = "a case where no callers information need to be computed", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "java.lang.String" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") }) public void noCallersInformationRequiredTest(String s) { System.out.println(s); @@ -368,7 +303,9 @@ public void noCallersInformationRequiredTest(String s) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "Hello, World_paintname((_PAD|_REFLECT|_REPEAT)?)?(_AlphaTest)?" + expectedStrings = "Hello, World_paintname((_PAD|_REFLECT|_REPEAT)?)?(_AlphaTest)?", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { @@ -576,10 +513,7 @@ public static String noReturnFunction2() { @StringDefinitionsCollection( value = "a case where a static property is read", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "will not be revealed here" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "will not be revealed here") }) public void getStaticFieldTest() { analyzeString(someKey); @@ -588,10 +522,7 @@ public void getStaticFieldTest() { @StringDefinitionsCollection( value = "a case where a String array field is read", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(January|February|March|April)" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(January|February|March|April)") }) public void getStringArrayField(int i) { analyzeString(monthNames[i]); @@ -602,18 +533,9 @@ public void getStringArrayField(int i) { @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" - ) + @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')); @@ -624,9 +546,7 @@ public void valueOfTest() { @StringDefinitionsCollection( value = "can handle virtual function calls", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") }) public void fromFunctionCall() { String className = getStringBuilderClassName(); @@ -638,7 +558,9 @@ public void fromFunctionCall() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?" + expectedStrings = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ), }) protected void setDebugFlags(String[] var1) { @@ -685,11 +607,14 @@ protected void setDebugFlags(String[] var1) { value = "an extensive example with many control structures", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): " + expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?" + expectedStrings = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void extensive(boolean cond) { @@ -753,5 +678,4 @@ private static String getHelperClass() { private String getApril() { return "April"; } - } 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 6e345bbf84..b542e7591b 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 @@ -160,11 +160,6 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities - // .filter(entity => entity._2.name.startsWith("whileWithBreak")) - // .filter(entity => entity._2.name.startsWith("tryCatchFinally")) - // .filter(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) - .filterNot(entity => entity._2.name.startsWith("switchNested")) - .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) // Currently broken L0 Tests .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) @@ -202,39 +197,29 @@ class L1StringAnalysisTest extends StringAnalysisTest { val as = executeAnalyses(LazyL1StringAnalysis) val entities = determineEntitiesToAnalyze(as.project) - .filterNot(entity => entity._2.name.startsWith("switchNested")) - .filterNot(entity => entity._2.name.startsWith("tryCatchFinallyWithThrowable")) // L0 Tests - .filterNot(entity => entity._2.name.startsWith("twoDefinitionsOneUsage")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("multipleDefSites")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" // Currently broken L0 Tests .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) // L1 Tests .filterNot(entity => entity._2.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("dependenciesWithinFinalizeTest")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("getPaintShader")) // Waits on string_concat and "substring" .filterNot(entity => entity._2.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" // Currently broken L1 Tests .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) .filterNot(entity => entity._2.name.startsWith("unknownHierarchyInstanceTest")) .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest1")) .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest2")) - .filterNot(entity => entity._2.name.startsWith("setDebugFlags")) .filterNot(entity => entity._2.name.startsWith("crissCrossExample")) - .filterNot(entity => entity._2.name.startsWith("breakContinueExamples")) - .filterNot(entity => entity._2.name.startsWith("simpleSecondStringBuilderRead")) - .filterNot(entity => entity._2.name.startsWith("complexSecondStringBuilderRead")) .filterNot(entity => entity._2.name.startsWith("directAppendConcatsWith2ndStringBuilder")) .filterNot(entity => entity._2.name.startsWith("parameterRead")) - .filterNot(entity => entity._2.name.startsWith("ifElseWithStringBuilder4")) - .filterNot(entity => entity._2.name.startsWith("ifElseWithStringBuilderWithFloatExpr")) .filterNot(entity => entity._2.name.startsWith("fromStringArray")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) } } 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 12d154268e..957e1b7e79 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 @@ -8,10 +8,7 @@ import scala.reflect.ClassTag import java.util.Arrays import scala.collection.{Set => SomeSet} import scala.collection.AbstractIterator -import scala.collection.mutable -import scala.collection.mutable.ListBuffer -import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.collection.immutable.IntTrieSet1 import org.opalj.collection.mutable.FixedSizedHashIDMap @@ -19,7 +16,6 @@ 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 org.opalj.log.GlobalLogContext import org.opalj.log.LogContext import org.opalj.log.OPALLogger.info @@ -728,115 +724,6 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( CFG[NewI, NewC](newCode, newNormalReturnNode, newAbnormalReturnNode, newCatchNodes, newBasicBlocks) } - // 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).toList: _*) - // 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 (exclude catch nodes here as this would detect loops where - // no actually are - to.filter { next => - val index = seenNodes.indexOf(next) - val isCatchNode = catchNodes.exists(_.handlerPC == next) - index > -1 && !isCatchNode && domTree.doesDominate(seenNodes(index), from) - }.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) - val hasDest = backedges.exists(_._2 == destIndex) - var removedBackedge = false - backedges.filter { - case (oldTo: Int, oldFrom: Int) => oldFrom == destIndex && oldTo < from - }.foreach { toRemove => removedBackedge = true; backedges -= toRemove } - if (!hasDest || removedBackedge) { - 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 - } - - /** - * @return Returns the post dominator tree of this CFG. - * - * @see [[PostDominatorTree.apply]] - */ - def postDominatorTree: PostDominatorTree = { - // Collect Indices of all nodes that lead into an artificial ExitNode - val exitNodes = basicBlocks.zipWithIndex.filter { next => - next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] - }.map(_._2) - - // If there is one unique such node, we have a 'uniqueExitNode', otherwise not - val uniqueExitNode = if (exitNodes.length == 1) Some(exitNodes.head) else None - - // Additional exit nodes are empty if there is only one exit node. Otherwise, they are collected as all nodes - // leading into artificial ExitNodes that are NOT the "normalReturnNode", i.e. abnormal termination. - // TODO: Verify this is intended behavior for PostDominatorTrees - val additionalExitNodes = if (uniqueExitNode.isDefined) EmptyIntTrieSet - else { - IntTrieSet( - basicBlocks - .zipWithIndex - .filter { next => - next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ - ExitNode - ] && !next._1.successors.head.equals(normalReturnNode) - } - .map(_._2) - ) - } - - // All nodes that have an ExitNode successor which is NOT the normalReturnNode are "additionalExitNodes" - PostDominatorTree( - uniqueExitNode, - i => exitNodes.contains(i), - additionalExitNodes, - // TODO: Correct function (just copied it from one of the tests)? - // TODO: Function seems correct to me - verify! - (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/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala b/OPAL/br/src/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala index 9713a099a6..4a47ad5140 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 @@ -7,7 +7,6 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import org.opalj.br.instructions.Instruction -import org.opalj.graphs.DominatorTree import org.opalj.io.writeAndOpen /** @@ -87,15 +86,11 @@ abstract class AbstractCFGTest extends AnyFunSpec with Matchers with BeforeAndAf assert((code.cfJoins -- cfJoins).isEmpty) } - /** - * 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. - */ + /** If the execution of `f` results in an exception the CFG is printed. */ def printCFGOnFailure( - method: Method, - code: Code, - cfg: CFG[Instruction, Code], - domTree: Option[DominatorTree] = None + method: Method, + code: Code, + cfg: CFG[Instruction, Code] )( f: => Unit )( @@ -107,9 +102,6 @@ abstract class AbstractCFGTest extends AnyFunSpec with Matchers with BeforeAndAf } 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 deleted file mode 100644 index f259ed5d9a..0000000000 --- a/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala +++ /dev/null @@ -1,119 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package cfg - -import java.net.URL - -import org.junit.runner.RunWith -import org.scalatestplus.junit.JUnitRunner - -import org.opalj.br.ClassHierarchy -import org.opalj.br.ObjectType -import org.opalj.br.TestSupport.biProject -import org.opalj.br.analyses.Project -import org.opalj.br.instructions.IF_ICMPNE -import org.opalj.br.instructions.IFEQ -import org.opalj.br.instructions.IFNE -import org.opalj.br.instructions.ILOAD - -/** - * 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) - } - } - - } - -} 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 ece280bcd9..976d7cbdbf 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala @@ -26,49 +26,6 @@ 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 - } - } /** 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 8cbfedaefb..5fc73e1230 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala @@ -2,8 +2,6 @@ package org.opalj package graphs -import scala.collection.mutable.ListBuffer - import org.opalj.collection.immutable.IntTrieSet /** @@ -37,27 +35,6 @@ 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) - } - } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 95f26bb07c..86b818542e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -34,8 +34,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathEleme import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType 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.SimplePathFinder 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 /** @@ -212,9 +212,7 @@ trait StringAnalysis extends FPCFAnalysis { if (initDefSites.isEmpty) { None } else { - val path = WindowPathFinder(tac).findPaths(initDefSites, value.definedBy.toArray.max) - val leanPath = path.makeLeanPath(value) - Some(leanPath) + Some(SimplePathFinder.findPath(tac).makeLeanPath(value)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 4a274f41b4..14cf467594 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -15,6 +15,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder import org.opalj.tac.fpcf.properties.TACAI /** @@ -70,6 +71,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac + if (SimplePathFinder.containsComplexControlFlow(tac)) { + return Result(state.entity, StringConstancyProperty.lb) + } + val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted @@ -95,7 +100,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis ) if (ep.isRefinable) { state.dependees = ep :: state.dependees - InterimResult.forLB( + return InterimResult.forLB( state.entity, StringConstancyProperty.lb, state.dependees.toSet, @@ -112,16 +117,13 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis val expr = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(state.computedLeanPath, puVar) - if (dependentVars.nonEmpty) { - dependentVars.keys.foreach { nextVar => - propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } + // Find DUVars that the analysis of the current entity depends on + findDependentVars(state.computedLeanPath, puVar).keys.foreach { nextVar => + propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) + case ep => + state.dependees = ep :: state.dependees } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index c3adc82acd..9f7c45eed3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -19,6 +19,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder import org.opalj.tac.fpcf.properties.TACAI /** @@ -78,6 +79,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state: ComputationState, iHandler: InterpretationHandler ): ProperPropertyComputationResult = { + if (SimplePathFinder.containsComplexControlFlow(state.tac)) { + return Result(state.entity, StringConstancyProperty.lb) + } + val puVar = state.entity._1 val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted @@ -99,6 +104,10 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } } + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uVar)(state.tac) + } + // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val ep = ps( @@ -107,7 +116,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { ) if (ep.isRefinable) { state.dependees = ep :: state.dependees - InterimResult.forLB( + return InterimResult.forLB( state.entity, StringConstancyProperty.lb, state.dependees.toSet, @@ -118,32 +127,31 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } } - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar)(state.tac) - } - - val call = state.tac.stmts(defSites.head).asAssignment.expr - var attemptFinalResultComputation = true - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val expr = state.tac.stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { // Find DUVars that the analysis of the current entity depends on - findDependentVars(state.computedLeanPath, puVar)(state).keys.foreach { nextVar => - val ep = propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) - ep match { + findDependentVars(state.computedLeanPath, puVar).keys.foreach { nextVar => + propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { case FinalEP(e, _) => state.dependees = state.dependees.filter(_.e != e) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - return computeFinalResult(state) - } else { - return getInterimResult(state, iHandler) - } - case _ => + case ep => state.dependees = ep :: state.dependees - attemptFinalResultComputation = false } } } + getPCsInPath(state.computedLeanPath).foreach { pc => + propertyStore( + InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), + StringConstancyProperty.key + ) match { + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) + case ep => + state.dependees = ep :: state.dependees + } + } + if (state.dependees.nonEmpty) { getInterimResult(state, iHandler) } else { 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 deleted file mode 100644 index e53c76099f..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ /dev/null @@ -1,1312 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package preprocessing - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -import org.opalj.br.ObjectType -import org.opalj.br.cfg.BasicBlock -import org.opalj.br.cfg.CatchNode -import org.opalj.br.cfg.CFG -import org.opalj.br.cfg.CFGNode - -/** - * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the - * scope of string definition analyses. - * - * @author Maximilian Rüsch - */ -abstract class AbstractPathFinder(tac: TAC) { - - val cfg: CFG[Stmt[V], TACStmts[V]] = tac.cfg - implicit val stmts: Array[Stmt[V]] = tac.stmts - - /** - * 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] - ): (Int, Int) = { - processedIfs(branchingSite) = () - - 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) = () - containsIf = true - } - } - - if (containsIf) { - stack.push(nextBlock) - } else { - // 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 => - // 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 { - // 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 - if (endSite == -1) { - endSite = nextBlock - } - case _ => - endSite = if (nextBlock > branchingSite) nextBlock - 1 - else - cfg.findNaturalLoops().find { - _.head == goto.targetStmt - }.get.last - } - } - case _ => - // No goto available => Jump after next block - var nextIf: Option[If[V]] = None - var i = nextBlock - while (i < cfg.code.instructions.length && nextIf.isEmpty) { - cfg.code.instructions(i) match { - case iff: If[V] => - nextIf = Some(iff) - processedIfs(i) = () - case _ => - } - i += 1 - } - endSite = if (nextIf.isDefined) nextIf.get.targetStmt - else { - stack.clear() - i - 1 - } - } - } - if (endSite < branchingSite) { - endSite = nextBlock - } - } - - (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] - ): (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 - val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt - for (i <- cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { - // 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) { - nextIfIndex = i - } - } - - var endIndex = nextPossibleIfBlock - 1 - if (nextIfIndex > -1 && !isHeadOfLoop(nextIfIndex, cfg.findNaturalLoops(), cfg)) { - processedIfs(nextIfIndex) = () - val (_, newEndIndex) = getStartAndEndIndexOfCondWithoutAlternative( - nextIfIndex, - processedIfs - ) - endIndex = newEndIndex - } - - // 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 seenElements: mutable.Map[Int, Unit] = mutable.Map() - val toVisit = mutable.Stack[Int](branchingSite) - while (toVisit.nonEmpty) { - val popped = toVisit.pop() - seenElements(popped) = () - 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(relevantSuccessors.filter { s => - s.nodeId != ifTarget && !seenElements.contains(s.nodeId) - }.map(_.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) { - 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 - // 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) = () - } - } - // How about CatchNodes? - case _ => - } - - (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 - * `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 == ObjectType.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 - } else if (cnSameStartPC.tail.isEmpty && !isThrowable) { - // 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 (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) - } - } else { - // Otherwise, the index after the try and all catches marks the end index (-1 - // to not already get the start index of the successor) - 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) 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 - } else { - 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 _ => blockIndex - }.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: 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 = if (fill) { - start.to(end).map(FlatPathElement.apply) - } else Seq.empty[SubPath] - (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() - 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 (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 - } - } - - 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)) - } - } - } - } - - // Append the "else" branch (if present) - if (pathType == NestedPathType.CondWithAlternative && startEndPairs.last._2 + 1 <= end) { - startEndPairs.append((startEndPairs.last._2 + 1, end)) - } - - val subPaths = startEndPairs.toSeq.flatMap { - case (startSubpath, endSubpath) => - val subpathElements = if (fill) { - startSubpath.to(endSubpath).map(FlatPathElement.apply) - } else Seq.empty[SubPath] - if (!fill || subpathElements.nonEmpty) - Some(NestedPathElement(subpathElements, None)) - else None - } - - val pathTypeToUse = - if (pathType == NestedPathType.CondWithAlternative && - startEndPairs.length == 1 - ) NestedPathType.CondWithoutAlternative - else pathType - - (Path(List(NestedPathElement(subPaths, Some(pathTypeToUse)))), 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, - pathType: NestedPathType.Value, - fill: Boolean - ): (Path, List[(Int, Int)]) = { - val switch = cfg.code.instructions(start).asSwitch - val caseStmts = ListBuffer[Int](switch.caseStmts.sorted: _*) - - // When the default stmt matches any case block, there is no explicit default defined - val containsDefault = pathType == NestedPathType.SwitchWithDefault - if (containsDefault) { - caseStmts.append(switch.defaultStmt) - } - - // Determine the start and end stmt indices of each case stmt - val startEndPairs = ListBuffer[(Int, Int)]() - 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 = startEndPairs.toSeq.map { pair => - val subpathElements = if (fill) { - Range.inclusive(pair._1, pair._2).map(FlatPathElement.apply) - } else Seq.empty[SubPath] - NestedPathElement(subpathElements, None) - } - (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) { - val endPC = if (cn.endPC >= 0) cn.endPC else cn.handlerPC - startEndPairs.append((cn.startPC, endPC)) - } - if (cn.catchType.isDefined && cn.catchType.get == ObjectType.Throwable) { - throwableElement = Some(cn) - } else { - catchBlockStartPCs.append(cn.handlerPC) - if (cn.startPC == start && 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 if (startEndPairs.nonEmpty) { - 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) 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 - } 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)) - } - } - } else { - // In some cases (sometimes when a throwable is involved) the successors are no catch - // nodes => Find the bounds now - 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)) - } - - var subPaths: Seq[SubPath] = startEndPairs.toSeq.map { - case (startSubpath, endSubpath) => - val subpathElements = if (fill) { - startSubpath.to(endSubpath).map(FlatPathElement.apply) - } else Seq.empty[SubPath] - NestedPathElement(subpathElements, None) - } - - // 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 = subPaths ++ (startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map(FlatPathElement.apply) - } - - ( - Path(List(NestedPathElement(subPaths, Some(NestedPathType.TryCatchFinally)))), - startEndPairs.toList - ) - } - - /** - * Generates a new [[NestedPathElement]] with a given number of inner [[NestedPathElement]]s. - */ - protected def generateNestPathElement( - numInnerElements: Int, - elementType: NestedPathType.Value - ): NestedPathElement = { - val innerElements = 0.until(numInnerElements).map(_ => NestedPathElement(Seq.empty, None)) - NestedPathElement(innerElements, Some(elementType)) - } - - /** - * 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]], - 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 - * (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) - - /** - * 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. - * 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 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 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. - */ - protected def isCondWithoutElse( - branchingSite: Int, - cfg: CFG[Stmt[V], TACStmts[V]], - processedIfs: mutable.Map[Int, Unit] - ): Boolean = { - val successorBlocks = cfg.bb(branchingSite).successors - // CatchNode exists => Regard it as conditional without alternative - if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { - processedIfs(branchingSite) = () - return false - } - - 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 - - // 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 - } - - 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 && !isHeadOfLoop(ifPos.head, cfg.findNaturalLoops(), cfg)) { - ifPos.head - } else { - -1 - } - case _ => -1 - } - - if (indexIf != -1) { - // For else-if constructs - 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 - // if-elseif). - 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.toList: _*) - while (toVisitStack.nonEmpty) { - val from = toVisitStack.pop() - val to = from.successors - if ((from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) && - from.nodeId >= branchingSite - ) { - reachableCount += 1 - } - seenNodes.append(from) - toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) - } - } - if (reachableCount > 1) { - true - } else { - processedIfs(branchingSite) = () - false - } - } - } - - /** - * 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, - alreadySeen: List[Int] = List() - ): Boolean = { - val stack = mutable.Stack(from) - val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_) = ()) - seenNodes(from) = () - - 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 = -1 - var 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) = () - } - } - } - } - - // When this part is reached, no path could be found - 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]): CSInfo = { - val csType = determineTypeOfIf(stmt, processedIfs) - val (startIndex, endIndex) = csType match { - case NestedPathType.Repetition => - processedIfs(stmt) = () - getStartAndEndIndexOfLoop(stmt) - case NestedPathType.CondWithoutAlternative => - getStartAndEndIndexOfCondWithoutAlternative(stmt, processedIfs) - case NestedPathType.CondWithAlternative => - getStartAndEndIndexOfCondWithAlternative(stmt, processedIfs) - case t => - throw new IllegalArgumentException(s"Unexpected nested path type: $t") - } - (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.SwitchWithDefault]] if the `switch` has a `default` case and - * [[NestedPathType.SwitchWithoutDefault]] otherwise. - * - * IMPROVE switch bound processing is broken for nested switch statements without statements after them. - * - * @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 - // From the last to the first one, find the first case that points after the switch - val caseGotoOption = caseStmts.findLast { caseIndex => cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] } - // 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.contains(switch.defaultStmt) - val pathType = if (containsDefault) NestedPathType.SwitchWithDefault - else NestedPathType.SwitchWithoutDefault - - (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, - processedIfs: mutable.Map[Int, Unit] - ): 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, processedIfs)) { - 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(startSites: List[Int], endSite: Int): List[CSInfo] = { - var foundCS = ListBuffer[CSInfo]() - // For a fast look-up which statements have already been processed - val processedIfs = mutable.Map[Int, Unit]() - val processedSwitches = mutable.Map[Int, Unit]() - val stack = mutable.Stack[CFGNode]() - val seenCFGNodes = mutable.Map[CFGNode, Unit]() - - startSites.reverse.foreach { site => - stack.push(cfg.bb(site)) - seenCFGNodes(cfg.bb(site)) = () - } - - while (stack.nonEmpty) { - val next = stack.pop() - seenCFGNodes(next) = () - - 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) = () - case _: Switch[V] if !processedSwitches.contains(i) => - foundCS.append(processSwitch(i)) - processedSwitches(i) = () - case _ => - } - } - case _ => - } - - 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) - } - } - - // 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 - val relevantTryCatchBlocks = determineTryCatchBounds() - // Filter out all blocks that completely surround the given start and end sites - .filter { - case (tryStart, tryEnd, _) => - !buildTryCatchPath(tryStart, tryEnd, fill = false)._2.exists { - case (nextInnerStart, nextInnerEnd) => - startSites.forall(_ >= nextInnerStart) && endSite <= nextInnerEnd - } - } - // Keep the try-catch blocks that are (partially) within the start and end sites - .filter { - case (tryStart, _, _) => startSites.exists(tryStart >= _) && tryStart <= endSite - } - - foundCS.appendAll(relevantTryCatchBlocks) - 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: CSInfo, - fill: Boolean - ): (Path, List[(Int, Int)]) = { - val start = toTransform._1 - val end = toTransform._2 - toTransform._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.SwitchWithDefault => - buildPathForSwitch(start, end, NestedPathType.SwitchWithDefault, fill) - case NestedPathType.SwitchWithoutDefault => - buildPathForSwitch(start, end, NestedPathType.SwitchWithoutDefault, 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 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.toMap)) - ))) - } - - /** - * 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 => - val nextTopCsInfo = nextTopEle.hierarchy.head._1.get - - // Build path up to the next control structure - val nextCSStart = nextTopCsInfo._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(nextTopCsInfo, fill = true) - // Control structures consist of only one element (NestedPathElement), thus "head" is enough - finalPath.append(subpath.elements.head) - } else { - val startIndex = nextTopCsInfo._1 - val endIndex = nextTopCsInfo._2 - val childrenPath = hierarchyToPath(children, startIndex, endIndex) - var insertIndex = 0 - val (subpath, startEndPairs) = buildPathForElement(nextTopCsInfo, 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 newElements = npe.element - var lastInsertedIndex = 0 - childrenPath.elements.foreach { nextEle => - if (isRepElement) { - newElements :+= nextEle - } else { - if (insertIndex < newElements.length) { - val innerNpe = newElements(insertIndex).asInstanceOf[NestedPathElement] - newElements = newElements.updated( - insertIndex, - NestedPathElement(innerNpe.element :+ nextEle, innerNpe.elementType) - ) - } - } - - lastInsertedIndex = nextEle match { - case fpe: FlatPathElement => fpe.stmtIndex(tac.pcToIndex) - case inner: NestedPathElement => Path.getLastElementInNPE(inner).stmtIndex(tac.pcToIndex) - // Compiler wants it but should never be the case! - case _ => -1 - } - if (insertIndex < startEndPairs.length && lastInsertedIndex >= startEndPairs(insertIndex)._2) { - insertIndex += 1 - } - } - // Fill the current NPE if necessary - if (insertIndex < startEndPairs.length) { - val currentToInsert = - (lastInsertedIndex + 1).to(startEndPairs(insertIndex)._2).map(FlatPathElement.apply) - if (isRepElement) { - newElements ++= currentToInsert - } else { - val innerNpe = newElements(insertIndex).asInstanceOf[NestedPathElement] - newElements = newElements.updated( - insertIndex, - NestedPathElement(innerNpe.element ++ currentToInsert, innerNpe.elementType) - ) - insertIndex += 1 - // Fill the rest NPEs if necessary - insertIndex.until(startEndPairs.length).foreach { i => - val innerNpe = newElements(i).asInstanceOf[NestedPathElement] - newElements = newElements.updated( - i, - NestedPathElement( - innerNpe.element ++ - startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement.apply), - innerNpe.elementType - ) - ) - } - } - } - // Make sure to have no empty lists - val subPathToAdd = NestedPathElement( - newElements.filter { - case npe: NestedPathElement => npe.element.nonEmpty - case _ => true - }, - npe.elementType - ) - finalPath.append(subPathToAdd) - } - indexLastCSEnd = nextTopCsInfo._2 + 1 - } - - finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement.apply)) - 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 - * 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. - * 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). - * @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 - * 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): Path -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala deleted file mode 100644 index e18beffdb9..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package preprocessing - -/** - * 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]]. - * - * @author Maximilian Rüsch - * - * @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(tac: TAC) extends AbstractPathFinder(tac) { - - /** - * 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. - * 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 startSite = cfg.startBlock.startPC - val endSite = cfg.code.instructions.length - 1 - val csInfo = findControlStructures(List(startSite), endSite) - if (csInfo.isEmpty) { - // In case the are no control structures, return a path from the first to the last element - Path(cfg.startBlock.startPC.until(cfg.code.instructions(endSite).pc).map(FlatPathElement.fromPC).toList) - } else { - // Otherwise, order the control structures and assign the corresponding path elements - val orderedCS = hierarchicallyOrderControlStructures(csInfo) - hierarchyToPath(orderedCS.hierarchy.head._2, startSite, endSite) - } - } -} 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 ed2614eec2..2d466f776d 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 @@ -47,39 +47,9 @@ object FlatPathElement extends SubPath { case object NestedPathType extends Enumeration { /** - * Used to mark any sort of loops. - */ - val Repetition: 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. + * A conditional that is executed in all cases, such as an `if` with an `else` block. */ 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 - - /** - * Use this type to mark a switch that does not contain a `default` statement. - */ - val SwitchWithoutDefault: NestedPathType.Value = Value - - /** - * Use this type to mark a switch that contains a `default` statement. - */ - val SwitchWithDefault: NestedPathType.Value = Value - - /** - * This type is to mark `try-catch` or `try-catch-finally` constructs. - */ - val TryCatchFinally: NestedPathType.Value = Value } /** @@ -205,10 +175,8 @@ case class Path(elements: List[SubPath]) { case npe: NestedPathElement => val leanedSubPath = makeLeanPathAcc(npe, relevantPCsMap) val keepAlternativeBranches = toProcess.elementType match { - case Some(NestedPathType.CondWithAlternative) | - Some(NestedPathType.SwitchWithDefault) | - Some(NestedPathType.TryCatchFinally) => true - case _ => false + case Some(NestedPathType.CondWithAlternative) => true + case _ => false } if (leanedSubPath.isDefined) { elements.append(leanedSubPath.get) @@ -278,9 +246,7 @@ case class Path(elements: List[SubPath]) { // body in any case (as there is no alternative branch to consider) TODO check loops again what is with loops that are never executed? if (leanPath.tail.isEmpty) { // TODO this throws if lean path is only one element long leanPath.head match { - case npe: NestedPathElement - if npe.elementType.get == NestedPathType.Repetition || - npe.element.tail.isEmpty => + case npe: NestedPathElement if npe.element.tail.isEmpty => leanPath.clear() leanPath.appendAll(removeOuterBranching(npe)) case _ => @@ -289,9 +255,7 @@ 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) leanPath.last match { - case npe: NestedPathElement - if npe.elementType.isDefined && - (npe.elementType.get != NestedPathType.TryCatchFinally && npe.elementType.get != NestedPathType.SwitchWithDefault) => + case npe: NestedPathElement if npe.elementType.isDefined => val newLast = stripUnnecessaryBranches(npe, endSite) leanPath.remove(leanPath.size - 1) leanPath.append(newLast) 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 7699518ba4..4113bfe8b2 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 @@ -13,7 +13,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringTreeCond import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString import org.opalj.br.fpcf.properties.string_definition.StringTreeNode import org.opalj.br.fpcf.properties.string_definition.StringTreeOr -import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler @@ -45,32 +44,16 @@ object PathTransformer { case npe: NestedPathElement => if (npe.elementType.isDefined) { npe.elementType.get match { - case NestedPathType.Repetition => - val processedSubPath = pathToStringTree(Path(npe.element.toList)) - Some(StringTreeRepetition(processedSubPath)) case _ => val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne) } if (processedSubPaths.nonEmpty) { npe.elementType.get match { - case NestedPathType.TryCatchFinally => - // 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.head)) - } - case NestedPathType.SwitchWithDefault | - NestedPathType.CondWithAlternative => + case NestedPathType.CondWithAlternative => if (npe.element.size == processedSubPaths.size) { Some(StringTreeOr(processedSubPaths)) } else { Some(StringTreeCond(StringTreeOr(processedSubPaths))) } - case NestedPathType.SwitchWithoutDefault => - Some(StringTreeCond(StringTreeOr(processedSubPaths))) - case NestedPathType.CondWithoutAlternative => - Some(StringTreeCond(StringTreeOr(processedSubPaths))) case _ => None } } else { @@ -110,8 +93,6 @@ object PathTransformer { ): StringTreeNode = { path.elements.size match { 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).getOrElse(StringTreeDynamicString) case _ => val children = path.elements.flatMap { pathToTreeAcc(_) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala new file mode 100644 index 0000000000..fb1608a692 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing + +object SimplePathFinder { + + /** + * Checks if the given TAC may contain any control structures + */ + def containsComplexControlFlow(tac: TAC): Boolean = { + tac.stmts.exists { + case _: If[V] => true + case _: Switch[V] => true + case _: Throw[V] => true + case _: Goto => true + case _: JSR => true + case _ => false + } || tac.cfg.catchNodes.nonEmpty + } + + /** + * Always returns a path from the first to the last statement pc in the CFG of the given TAC + */ + def findPath(tac: TAC): Path = { + val cfg = tac.cfg + Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(FlatPathElement.fromPC).toList) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala deleted file mode 100644 index 8ace58d206..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala +++ /dev/null @@ -1,69 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package preprocessing - -/** - * 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.) - * - * @author Maximilian Rüsch - * - * @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! - */ -case class WindowPathFinder(tac: TAC) extends AbstractPathFinder(tac) { - - /** - * 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 - } - if (startSite.isEmpty) { - startSite = Some(0) - } - } else { - startSite = Some(startSites.head) - } - - val csInfo = findControlStructures(List(startSite.get), endSite) - if (csInfo.isEmpty) { - Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(FlatPathElement.fromPC).toList) - } else { - // Otherwise, order the control structures and assign the corresponding path elements - val orderedCS = hierarchicallyOrderControlStructures(csInfo) - hierarchyToPath(orderedCS.hierarchy.head._2, startSite.get, endSite) - } - } -} From 623e5f76a53e68017e1fc8b55948863ee71fd511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 3 Apr 2024 20:20:24 +0200 Subject: [PATCH 395/583] Turn around string analysis to produce UBs instead of LBs --- .../string_analysis/StringInterpreter.scala | 12 ++++++------ .../string_analysis/l0/L0StringAnalysis.scala | 4 ++-- .../L0ArrayAccessInterpreter.scala | 4 ++-- .../interpretation/L0NewArrayInterpreter.scala | 4 ++-- .../L0NonVirtualMethodCallInterpreter.scala | 4 ++-- .../L0StaticFunctionCallInterpreter.scala | 4 ++-- .../L0VirtualFunctionCallInterpreter.scala | 16 ++++++++-------- .../string_analysis/l1/L1StringAnalysis.scala | 4 ++-- .../interpretation/L1FieldReadInterpreter.scala | 4 ++-- .../preprocessing/PathTransformer.scala | 4 ++-- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 91687a2ca1..9e7e25949e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -51,18 +51,18 @@ trait StringInterpreter { if (updatedDependees.forall(_.isFinal)) { finalResult(updatedDependees.asInstanceOf[Seq[SomeFinalEP]]) } else { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(depender.pc)(depender.state), - StringConstancyProperty.lb, - depender.dependees.toSet, + StringConstancyProperty.ub, + depender.dependees.filter(_.isRefinable).toSet, awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) ) } } else { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(depender.pc)(depender.state), - StringConstancyProperty.lb, - depender.dependees.toSet, + StringConstancyProperty.ub, + depender.dependees.filter(_.isRefinable).toSet, awaitAllFinalContinuation(depender, finalResult) ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 14cf467594..a84d0e66d9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -100,9 +100,9 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis ) if (ep.isRefinable) { state.dependees = ep :: state.dependees - return InterimResult.forLB( + return InterimResult.forUB( state.entity, - StringConstancyProperty.lb, + StringConstancyProperty.ub, state.dependees.toSet, continuation(state, iHandler) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index bb53ac4ec6..2460d43b86 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -31,9 +31,9 @@ case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( EPSDepender(instr, pc, state, results), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index a82675cc31..1b09084e0a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -46,9 +46,9 @@ case class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { } if (allResults.exists(_.isRefinable)) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, allResults.filter(_.isRefinable).toSet, awaitAllFinalContinuation( EPSDepender(instr, pc, state, allResults), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 3abd9577e3..22eebb499a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -42,9 +42,9 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringIn if (results.forall(_.isFinal)) { finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) } else { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, results.toSet, awaitAllFinalContinuation( EPSDepender(init, pc, state, results), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 3c50b5989c..3f00d4b5a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -94,9 +94,9 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends St ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } if (results.exists(_.isRefinable)) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, results.filter(_.isRefinable).toSet, awaitAllFinalContinuation( EPSDepender(call, call.pc, state, results), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 4e9ed502f9..2be55dc276 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -100,17 +100,17 @@ case class L0VirtualFunctionCallInterpreter( computeFinalResult(pc, sciP.sci) case iep: InterimEP[_, _] if eps.pk == StringConstancyProperty.key => - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - iep.lb.asInstanceOf[StringConstancyProperty], + iep.ub.asInstanceOf[StringConstancyProperty], Set(eps), computeResult ) case _ if eps.pk == StringConstancyProperty.key => - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, Set(eps), computeResult ) @@ -217,9 +217,9 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter appendState: AppendCallState ): ProperPropertyComputationResult = { if (appendState.hasDependees) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(appendState.defSitePC)(appendState.state), - StringConstancyProperty.lb, + StringConstancyProperty.ub, appendState.dependees.toSet, continuation(appendState) ) @@ -292,9 +292,9 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre } if (receiverResults.exists(_.isRefinable)) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.lb, + StringConstancyProperty.ub, receiverResults.toSet, awaitAllFinalContinuation( EPSDepender(substringCall, substringCall.pc, state, receiverResults), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 9f7c45eed3..6cf5e632d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -116,9 +116,9 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { ) if (ep.isRefinable) { state.dependees = ep :: state.dependees - return InterimResult.forLB( + return InterimResult.forUB( state.entity, - StringConstancyProperty.lb, + StringConstancyProperty.ub, state.dependees.toSet, continuation(state, iHandler) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index b601220965..df7becf13f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -143,9 +143,9 @@ case class L1FieldReadInterpreter( private def tryComputeFinalResult(implicit accessState: FieldReadState): ProperPropertyComputationResult = { if (accessState.hasDependees) { - InterimResult.forLB( + InterimResult.forUB( InterpretationHandler.getEntityForPC(accessState.defSitePC)(accessState.state), - StringConstancyProperty.lb, + StringConstancyProperty.ub, accessState.dependees.toSet, continuation(accessState) ) 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 4113bfe8b2..1e6e09134d 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 @@ -13,8 +13,8 @@ import org.opalj.br.fpcf.properties.string_definition.StringTreeCond import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString import org.opalj.br.fpcf.properties.string_definition.StringTreeNode import org.opalj.br.fpcf.properties.string_definition.StringTreeOr -import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** @@ -37,7 +37,7 @@ object PathTransformer { InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac), StringConstancyProperty.key ) match { - case FinalP(scp) => scp.sci + case UBP(scp) => scp.sci case _ => StringConstancyInformation.lb } Option.unless(sci.isTheNeutralElement)(sci.tree) From d0ffa12d94e7ffaeed04126d3db0feaab440cfdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 4 Apr 2024 12:41:51 +0200 Subject: [PATCH 396/583] Simplify all string analysis levels --- .../string_analysis/StringAnalysis.scala | 37 ++++--- .../string_analysis/l0/L0StringAnalysis.scala | 81 +-------------- .../string_analysis/l1/L1StringAnalysis.scala | 98 +++---------------- .../preprocessing/PathTransformer.scala | 2 +- 4 files changed, 40 insertions(+), 178 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 86b818542e..24f2e4ce97 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -47,7 +47,21 @@ trait StringAnalysis extends FPCFAnalysis { val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - def analyze(data: SContext): ProperPropertyComputationResult + def analyze(data: SContext): ProperPropertyComputationResult = { + val state = ComputationState(declaredMethods(data._2), data) + + val tacaiEOptP = ps(data._2, TACAI.key) + if (tacaiEOptP.isRefinable) { + state.tacDependee = Some(tacaiEOptP) + getInterimResult(state) + } else if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + Result(state.entity, StringConstancyProperty.lb) + } else { + state.tac = tacaiEOptP.ub.tac.get + determinePossibleStrings(state) + } + } /** * Takes the `data` an analysis was started with as well as a computation `state` and determines @@ -55,8 +69,7 @@ trait StringAnalysis extends FPCFAnalysis { * [[InterimResult]] depending on whether other information needs to be computed first. */ protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState, - iHandler: InterpretationHandler + state: ComputationState ): ProperPropertyComputationResult /** @@ -68,10 +81,7 @@ trait StringAnalysis extends FPCFAnalysis { * @return Returns a final result if (already) available. Otherwise, an intermediate result will * be returned. */ - protected[this] def continuation( - state: ComputationState, - iHandler: InterpretationHandler - )(eps: SomeEPS): ProperPropertyComputationResult = { + protected[this] def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) if eps.pk.equals(TACAI.key) && @@ -79,7 +89,7 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee.get == eps => state.tac = tac.tac.get state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) - determinePossibleStrings(state, iHandler) + determinePossibleStrings(state) case FinalEP(e, _) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = state.dependees.filter(_.e != e) @@ -88,10 +98,10 @@ trait StringAnalysis extends FPCFAnalysis { if (state.dependees.isEmpty) { computeFinalResult(state) } else { - getInterimResult(state, iHandler) + getInterimResult(state) } case _ => - getInterimResult(state, iHandler) + getInterimResult(state) } } @@ -116,16 +126,13 @@ trait StringAnalysis extends FPCFAnalysis { ) } - protected def getInterimResult( - state: ComputationState, - iHandler: InterpretationHandler - ): InterimResult[StringConstancyProperty] = { + protected def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { InterimResult( state.entity, StringConstancyProperty.lb, computeNewUpperBound(state), state.dependees.toSet, - continuation(state, iHandler) + continuation(state) ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index a84d0e66d9..6b444627b7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -16,58 +16,14 @@ import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder -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 is - * 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 - * [[org.opalj.br.fpcf.properties.string_definition.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 - * [[org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer]]. - * - * @author Patrick Mell + * @author Maximilian Rüsch */ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - override def analyze(data: SContext): ProperPropertyComputationResult = { - val state = ComputationState(declaredMethods(data._2), data) - val iHandler = L0InterpretationHandler() - - val tacaiEOptP = ps(data._2, TACAI.key) - if (tacaiEOptP.isRefinable) { - state.tacDependee = Some(tacaiEOptP) - return getInterimResult(state, iHandler) - } - - if (tacaiEOptP.ub.tac.isEmpty) { - // No TAC available, e.g., because the method has no body - return Result(state.entity, StringConstancyProperty.lb) - } - - state.tac = tacaiEOptP.ub.tac.get - determinePossibleStrings(state, iHandler) - } - override protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState, - iHandler: InterpretationHandler + state: ComputationState ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac @@ -75,23 +31,9 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis return Result(state.entity, StringConstancyProperty.lb) } - val puVar = state.entity._1 - val uVar = puVar.toValueOriginForm(tac.pcToIndex) + val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted - if (defSites.exists(_ < 0)) { - if (InterpretationHandler.isStringConstExpression(uVar)) { - // We can evaluate string const expressions as function parameters - } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { - val numType = uVar.value.asPrimitiveValue.primitiveType.toJava - val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) - return Result(state.entity, StringConstancyProperty(sci)) - } else { - // StringBuilders as parameters are currently not evaluated - return Result(state.entity, StringConstancyProperty.lb) - } - } - // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val ep = ps( @@ -104,7 +46,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis state.entity, StringConstancyProperty.ub, state.dependees.toSet, - continuation(state, iHandler) + continuation(state) ) } else { return Result(state.entity, StringConstancyProperty(ep.asFinal.p.sci)) @@ -115,19 +57,6 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis state.computedLeanPath = computeLeanPath(uVar) } - val expr = tac.stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - // Find DUVars that the analysis of the current entity depends on - findDependentVars(state.computedLeanPath, puVar).keys.foreach { nextVar => - propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } - } - } - getPCsInPath(state.computedLeanPath).foreach { pc => propertyStore( InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), @@ -143,7 +72,7 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis if (state.dependees.isEmpty) { computeFinalResult(state) } else { - getInterimResult(state, iHandler) + getInterimResult(state) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 6cf5e632d2..a2ef90f536 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -20,98 +20,33 @@ import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder -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 [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]], 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 [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]] 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 [[L1InterpretationHandler]]) - * 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 + * @author Maximilian Rüsch */ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - override def analyze(data: SContext): ProperPropertyComputationResult = { - // IMPROVE enable handling call string contexts here (build a chain, probably via SContext) - val state = ComputationState(declaredMethods(data._2), data) - val iHandler = L1InterpretationHandler(project, ps) - - val tacaiEOptP = ps(data._2, TACAI.key) - if (tacaiEOptP.isRefinable) { - state.tacDependee = Some(tacaiEOptP) - return getInterimResult(state, iHandler) - } - - if (tacaiEOptP.ub.tac.isEmpty) { - // No TAC available, e.g., because the method has no body - return Result(state.entity, StringConstancyProperty.lb) - } - - state.tac = tacaiEOptP.ub.tac.get - - determinePossibleStrings(state, iHandler) - } - /** * 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 * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. */ override protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState, - iHandler: InterpretationHandler + state: ComputationState ): ProperPropertyComputationResult = { - if (SimplePathFinder.containsComplexControlFlow(state.tac)) { + implicit val tac: TAC = state.tac + + if (SimplePathFinder.containsComplexControlFlow(tac)) { return Result(state.entity, StringConstancyProperty.lb) } - val puVar = state.entity._1 - val uVar = puVar.toValueOriginForm(state.tac.pcToIndex) + val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted - if (state.tac == null) { - return getInterimResult(state, iHandler) - } - - if (defSites.exists(_ < 0)) { - if (InterpretationHandler.isStringConstExpression(uVar)) { - // We can evaluate string const expressions as function parameters - } else if (StringAnalysis.isSupportedPrimitiveNumberType(uVar)) { - val numType = uVar.value.asPrimitiveValue.primitiveType.toJava - val sci = StringAnalysis.getDynamicStringInformationForNumberType(numType) - return Result(state.entity, StringConstancyProperty(sci)) - } else { - // StringBuilders as parameters are currently not evaluated - return Result(state.entity, StringConstancyProperty.lb) - } - } - - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar)(state.tac) - } - // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val ep = ps( - InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, state.tac), + InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac), StringConstancyProperty.key ) if (ep.isRefinable) { @@ -120,29 +55,20 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { state.entity, StringConstancyProperty.ub, state.dependees.toSet, - continuation(state, iHandler) + continuation(state) ) } else { return Result(state.entity, ep.asFinal.p) } } - val expr = state.tac.stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - // Find DUVars that the analysis of the current entity depends on - findDependentVars(state.computedLeanPath, puVar).keys.foreach { nextVar => - propertyStore((nextVar, state.entity._2), StringConstancyProperty.key) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } - } + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uVar) } getPCsInPath(state.computedLeanPath).foreach { pc => propertyStore( - InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), + InterpretationHandler.getEntityForPC(pc, state.dm, tac), StringConstancyProperty.key ) match { case FinalEP(e, _) => @@ -153,7 +79,7 @@ class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { } if (state.dependees.nonEmpty) { - getInterimResult(state, iHandler) + getInterimResult(state) } else { computeFinalResult(state) } 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 1e6e09134d..7642dee25f 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 @@ -38,7 +38,7 @@ object PathTransformer { StringConstancyProperty.key ) match { case UBP(scp) => scp.sci - case _ => StringConstancyInformation.lb + case _ => StringConstancyInformation.lb } Option.unless(sci.isTheNeutralElement)(sci.tree) case npe: NestedPathElement => From dffb636c0b72d96f3948836ce82cbe077ebb13f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 15 Apr 2024 22:13:15 +0200 Subject: [PATCH 397/583] Further simplify all string analysis levels --- .../string_analysis/ComputationState.scala | 3 +- .../string_analysis/StringAnalysis.scala | 168 ++++++------------ .../string_analysis/l0/L0StringAnalysis.scala | 64 +------ .../string_analysis/l1/L1StringAnalysis.scala | 69 +------ 4 files changed, 61 insertions(+), 243 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 22dec2e645..1cd1803e73 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -8,7 +8,6 @@ package string_analysis import org.opalj.br.DefinedMethod import org.opalj.br.Method -import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path @@ -36,7 +35,7 @@ case class ComputationState(dm: DefinedMethod, entity: SContext) { /** * If not empty, this routine can only produce an intermediate result */ - var dependees: List[EOptionP[Entity, Property]] = List() + var dependees: List[EOptionP[DefSiteEntity, Property]] = List() } case class DefSiteState(pc: Int, dm: DefinedMethod, tac: TAC) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 24f2e4ce97..e6d61688f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -5,9 +5,6 @@ package fpcf package analyses package string_analysis -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods @@ -68,9 +65,57 @@ trait StringAnalysis extends FPCFAnalysis { * the possible string values. This method returns either a final [[Result]] or an * [[InterimResult]] depending on whether other information needs to be computed first. */ - protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState - ): ProperPropertyComputationResult + private def determinePossibleStrings(implicit state: ComputationState): ProperPropertyComputationResult = { + implicit val tac: TAC = state.tac + + val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) + val defSites = uVar.definedBy.toArray.sorted + + // Interpret a function / method parameter using the parameter information in state + if (defSites.head < 0) { + val ep = ps( + InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac), + StringConstancyProperty.key + ) + if (ep.isRefinable) { + state.dependees = ep :: state.dependees + return InterimResult.forUB( + state.entity, + StringConstancyProperty.ub, + state.dependees.toSet, + continuation(state) + ) + } else { + return Result(state.entity, ep.asFinal.p) + } + } + + if (SimplePathFinder.containsComplexControlFlow(tac)) { + return Result(state.entity, StringConstancyProperty.lb) + } + + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uVar) + } + + getPCsInPath(state.computedLeanPath).foreach { pc => + propertyStore( + InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), + StringConstancyProperty.key + ) match { + case FinalEP(e, _) => + state.dependees = state.dependees.filter(_.e != e) + case ep => + state.dependees = ep :: state.dependees + } + } + + if (state.dependees.isEmpty) { + computeFinalResult(state) + } else { + getInterimResult(state) + } + } /** * Continuation function for this analysis. @@ -91,7 +136,7 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) determinePossibleStrings(state) - case FinalEP(e, _) if eps.pk.equals(StringConstancyProperty.key) => + case FinalEP(e: DefSiteEntity, _) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run @@ -117,7 +162,7 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - protected def computeFinalResult(state: ComputationState): Result = { + private def computeFinalResult(state: ComputationState): Result = { Result( state.entity, StringConstancyProperty(StringConstancyInformation( @@ -126,7 +171,7 @@ trait StringAnalysis extends FPCFAnalysis { ) } - protected def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { + private def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { InterimResult( state.entity, StringConstancyProperty.lb, @@ -146,35 +191,7 @@ trait StringAnalysis extends FPCFAnalysis { } } - /** - * 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 state The current state of the computation. - * @return Returns `true` if all values computed for the path are final results. - */ - protected def computeResultsForPath(p: Path)(implicit state: ComputationState): Boolean = { - var hasFinalResult = true - p.elements.foreach { - case fpe: FlatPathElement => - val eOptP = - ps(InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac), StringConstancyProperty.key) - if (eOptP.isRefinable) { - hasFinalResult = false - } - case npe: NestedPathElement => - hasFinalResult = hasFinalResult && computeResultsForPath(Path(npe.element.toList))(state) - case _ => - } - - hasFinalResult - } - - /** - * Wrapper function for [[computeLeanPathForStringConst]] and [[computeLeanPathForStringBuilder]]. - */ - protected def computeLeanPath(value: V)(implicit tac: TAC): Path = { + private def computeLeanPath(value: V)(implicit tac: TAC): Path = { val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { computeLeanPathForStringConst(value)(tac.stmts) @@ -188,10 +205,7 @@ trait StringAnalysis extends FPCFAnalysis { } } - /** - * This function computes the lean path for a [[V]] which is required to be a string expression. - */ - protected def computeLeanPathForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Path = { + private def computeLeanPathForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Path = { val defSites = value.definedBy.toArray.sorted val element = if (defSites.length == 1) { FlatPathElement(defSites.head) @@ -205,16 +219,7 @@ trait StringAnalysis extends FPCFAnalysis { Path(List(element)) } - /** - * This function computes the lean path for a [[V]] which is required to stem from a - * `String{Builder, Buffer}#toString()` call. For this, the `tac` of the method, in which `value` 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)`. - */ - protected def computeLeanPathForStringBuilder(value: V)(implicit tac: TAC): Option[Path] = { + private def computeLeanPathForStringBuilder(value: V)(implicit tac: TAC): Option[Path] = { val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) if (initDefSites.isEmpty) { None @@ -223,56 +228,7 @@ trait StringAnalysis extends FPCFAnalysis { } } - /** - * Finds [[PUVar]]s the string constancy information computation for the given [[Path]] depends on. Enables passing - * an entity to ignore (usually the entity for which the path was created so it does not depend on itself). - * - * @return A mapping from dependent [[PUVar]]s to the [[FlatPathElement]] indices they occur in. - */ - protected def findDependentVars(path: Path, ignore: SEntity)( // We may need to register the old path with them - implicit state: ComputationState): mutable.LinkedHashMap[SEntity, Int] = { - val stmts = state.tac.stmts - - def findDependeesAcc(subpath: SubPath): ListBuffer[(SEntity, Int)] = { - val foundDependees = ListBuffer[(SEntity, 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.stmtIndex(state.tac.pcToIndex)) 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 - // TODO check support for passing nested string builder directly (e.g. with a test case) - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - foundDependees.append((param.toPersistentForm(stmts), fpe.pc)) - } - } - } - case _ => - } - foundDependees - case npe: NestedPathElement => - foundDependees.appendAll(npe.element.flatMap { findDependeesAcc }) - foundDependees - case _ => foundDependees - } - } - - val dependees = mutable.LinkedHashMap[SEntity, Int]() - path.elements.foreach { nextSubpath => - findDependeesAcc(nextSubpath).foreach { nextPair => - if (ignore != nextPair._1) { - dependees.put(nextPair._1, nextPair._2) - } - } - } - dependees - } - - protected def getPCsInPath(path: Path): Iterable[Int] = { + private def getPCsInPath(path: Path): Iterable[Int] = { def getDefSitesOfPathAcc(subpath: SubPath): Iterable[Int] = { subpath match { case fpe: FlatPathElement => Seq(fpe.pc) @@ -333,14 +289,6 @@ object StringAnalysis { } def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) - - def getDynamicStringInformationForNumberType(numberType: String): StringConstancyInformation = { - numberType match { - case "short" | "int" => StringConstancyInformation.dynamicInt - case "float" | "double" => StringConstancyInformation.dynamicFloat - case _ => StringConstancyInformation.lb - } - } } sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 6b444627b7..8b75475c8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -7,75 +7,13 @@ package string_analysis package l0 import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder /** * @author Maximilian Rüsch */ -class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - - override protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState - ): ProperPropertyComputationResult = { - implicit val tac: TAC = state.tac - - if (SimplePathFinder.containsComplexControlFlow(tac)) { - return Result(state.entity, StringConstancyProperty.lb) - } - - val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) - val defSites = uVar.definedBy.toArray.sorted - - // Interpret a function / method parameter using the parameter information in state - if (defSites.head < 0) { - val ep = ps( - InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, state.tac), - StringConstancyProperty.key - ) - if (ep.isRefinable) { - state.dependees = ep :: state.dependees - return InterimResult.forUB( - state.entity, - StringConstancyProperty.ub, - state.dependees.toSet, - continuation(state) - ) - } else { - return Result(state.entity, StringConstancyProperty(ep.asFinal.p.sci)) - } - } - - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar) - } - - getPCsInPath(state.computedLeanPath).foreach { pc => - propertyStore( - InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), - StringConstancyProperty.key - ) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } - } - - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } - } -} +class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis object LazyL0StringAnalysis extends LazyStringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index a2ef90f536..87c5ee2bcf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -9,82 +9,15 @@ package l1 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder /** * @author Maximilian Rüsch */ -class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - - /** - * 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 - * [[org.opalj.fpcf.InterimResult]] depending on whether other information needs to be computed first. - */ - override protected[string_analysis] def determinePossibleStrings(implicit - state: ComputationState - ): ProperPropertyComputationResult = { - implicit val tac: TAC = state.tac - - if (SimplePathFinder.containsComplexControlFlow(tac)) { - return Result(state.entity, StringConstancyProperty.lb) - } - - val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) - val defSites = uVar.definedBy.toArray.sorted - - // Interpret a function / method parameter using the parameter information in state - if (defSites.head < 0) { - val ep = ps( - InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac), - StringConstancyProperty.key - ) - if (ep.isRefinable) { - state.dependees = ep :: state.dependees - return InterimResult.forUB( - state.entity, - StringConstancyProperty.ub, - state.dependees.toSet, - continuation(state) - ) - } else { - return Result(state.entity, ep.asFinal.p) - } - } - - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar) - } - - getPCsInPath(state.computedLeanPath).foreach { pc => - propertyStore( - InterpretationHandler.getEntityForPC(pc, state.dm, tac), - StringConstancyProperty.key - ) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } - } - - if (state.dependees.nonEmpty) { - getInterimResult(state) - } else { - computeFinalResult(state) - } - } -} +class L1StringAnalysis(val project: SomeProject) extends StringAnalysis object L1StringAnalysis { From 231fa30a19d089a84e2f246a8268e7013986562b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 15 Apr 2024 22:41:08 +0200 Subject: [PATCH 398/583] Remove nested paths from string analysis for now --- .../string_analysis/ComputationState.scala | 2 +- .../string_analysis/StringAnalysis.scala | 48 ++--- .../string_analysis/preprocessing/Path.scala | 177 +----------------- .../preprocessing/PathTransformer.scala | 71 ++----- 4 files changed, 35 insertions(+), 263 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index 1cd1803e73..fb1e42a9b6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -28,7 +28,7 @@ case class ComputationState(dm: DefinedMethod, entity: SContext) { /** * The computed lean path that corresponds to the given entity */ - var computedLeanPath: Path = _ + var computedLeanPaths: Seq[Path] = _ var tacDependee: Option[EOptionP[Method, TACAI]] = _ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index e6d61688f4..762b28bb31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -27,8 +27,6 @@ import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler 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.NestedPathType 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.SimplePathFinder @@ -94,11 +92,11 @@ trait StringAnalysis extends FPCFAnalysis { return Result(state.entity, StringConstancyProperty.lb) } - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uVar) + if (state.computedLeanPaths == null) { + state.computedLeanPaths = computeLeanPaths(uVar) } - getPCsInPath(state.computedLeanPath).foreach { pc => + state.computedLeanPaths.flatMap(getPCsInPath).distinct.foreach { pc => propertyStore( InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), StringConstancyProperty.key @@ -166,7 +164,7 @@ trait StringAnalysis extends FPCFAnalysis { Result( state.entity, StringConstancyProperty(StringConstancyInformation( - tree = PathTransformer.pathToStringTree(state.computedLeanPath)(state, ps).simplify + tree = PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify )) ) } @@ -182,58 +180,46 @@ trait StringAnalysis extends FPCFAnalysis { } private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { - if (state.computedLeanPath != null) { + if (state.computedLeanPaths != null) { StringConstancyProperty(StringConstancyInformation( - tree = PathTransformer.pathToStringTree(state.computedLeanPath)(state, ps).simplify + tree = PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify )) } else { StringConstancyProperty.lb } } - private def computeLeanPath(value: V)(implicit tac: TAC): Path = { + private def computeLeanPaths(value: V)(implicit tac: TAC): Seq[Path] = { val defSites = value.definedBy.toArray.sorted if (defSites.head < 0) { - computeLeanPathForStringConst(value)(tac.stmts) + computeLeanPathsForStringConst(value)(tac.stmts) } else { val call = tac.stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - computeLeanPathForStringBuilder(value).get + computeLeanPathsForStringBuilder(value) } else { - computeLeanPathForStringConst(value)(tac.stmts) + computeLeanPathsForStringConst(value)(tac.stmts) } } } - private def computeLeanPathForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Path = { - val defSites = value.definedBy.toArray.sorted - val element = if (defSites.length == 1) { - FlatPathElement(defSites.head) - } else { - // Create alternative branches with intermediate None-Type nested path elements - NestedPathElement( - defSites.toIndexedSeq.map { ds => NestedPathElement(Seq(FlatPathElement(ds)), None) }, - Some(NestedPathType.CondWithAlternative) - ) - } - Path(List(element)) - } + private def computeLeanPathsForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Seq[Path] = + value.definedBy.toList.sorted.map(ds => Path(List(FlatPathElement(ds)))) - private def computeLeanPathForStringBuilder(value: V)(implicit tac: TAC): Option[Path] = { + private def computeLeanPathsForStringBuilder(value: V)(implicit tac: TAC): Seq[Path] = { val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) if (initDefSites.isEmpty) { - None + Seq.empty } else { - Some(SimplePathFinder.findPath(tac).makeLeanPath(value)) + Seq(SimplePathFinder.findPath(tac).makeLeanPath(value)) } } private def getPCsInPath(path: Path): Iterable[Int] = { def getDefSitesOfPathAcc(subpath: SubPath): Iterable[Int] = { subpath match { - case fpe: FlatPathElement => Seq(fpe.pc) - case npe: NestedPathElement => npe.element.flatMap(getDefSitesOfPathAcc) - case _ => Seq.empty + case fpe: FlatPathElement => Seq(fpe.pc) + case _ => Seq.empty } } 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 2d466f776d..7a04212b62 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 @@ -6,8 +6,6 @@ package analyses package string_analysis package preprocessing -import scala.annotation.tailrec - import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -15,7 +13,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.value.ValueInformation /** - * @author Patrick Mell + * @author Maximilian Rüsch */ /** @@ -38,30 +36,8 @@ object FlatPathElement extends SubPath { def unapply(fpe: FlatPathElement)(implicit pcToIndex: Array[Int]): Some[Int] = Some(fpe.stmtIndex) def fromPC(pc: Int) = new FlatPathElement(pc) - def invalid = new FlatPathElement(-1) -} - -/** - * Identifies the nature of a nested path element. - */ -case object NestedPathType extends Enumeration { - - /** - * A conditional that is executed in all cases, such as an `if` with an `else` block. - */ - val CondWithAlternative: NestedPathType.Value = 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: Seq[SubPath], - elementType: Option[NestedPathType.Value] -) extends SubPath - /** * Models a path by assembling it out of [[SubPath]] elements. * @@ -99,100 +75,6 @@ case class Path(elements: List[SubPath]) { defAndUses.toList.sorted } - /** - * Checks whether a [[FlatPathElement]] with the given `element` is contained in the given `subpath`. This function - * does a deep search, i.e., will also find the element if it is contained within [[NestedPathElement]]s. - */ - private def containsPathElementWithPC(subpath: NestedPathElement, pc: Int): Boolean = { - subpath.element.foldLeft(false) { (old: Boolean, nextSubpath: SubPath) => - old || (nextSubpath match { - case fpe: FlatPathElement => fpe.pc == pc - case npe: NestedPathElement => containsPathElementWithPC(npe, pc) - case e => throw new IllegalStateException(s"Unexpected path element $e") - }) - } - } - - /** - * Takes a [[NestedPathElement]] and removes the outermost nesting, i.e., the path contained - * in `npe` will be the path being returned. - */ - @tailrec private def removeOuterBranching(npe: NestedPathElement): Seq[SubPath] = { - if (npe.element.tail.isEmpty) { - npe.element.head match { - case innerNpe: NestedPathElement => removeOuterBranching(innerNpe) - case fpe: SubPath => Seq(fpe) - } - } else { - 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. - * 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 = { - val strippedElements = npe.element.map { - case innerNpe @ NestedPathElement(_, elementType) if elementType.isEmpty => - if (!containsPathElementWithPC(innerNpe, endSite)) { - NestedPathElement(Seq.empty, None) - } else { - innerNpe - } - case innerNpe: NestedPathElement => - stripUnnecessaryBranches(innerNpe, endSite) - case pe => pe - } - NestedPathElement(strippedElements, npe.elementType) - } - - /** - * Accumulator function for transforming a path into its lean equivalent. This function turns - * [[NestedPathElement]]s into lean [[NestedPathElement]]s and is a helper function of - * [[makeLeanPath]]. - * - * @param toProcess The NestedPathElement to turn into its lean equivalent. - * @param relevantPCsMap Serves as a constant time look-up table to include only pcs that are of interest, in this - * case: That belong to some object. - * - * @return In case a (sub) path is empty, `None` is returned and otherwise the lean (sub) path. - */ - private def makeLeanPathAcc( - toProcess: NestedPathElement, - relevantPCsMap: Map[Int, Unit] - ): Option[NestedPathElement] = { - val elements = ListBuffer[SubPath]() - - toProcess.element.foreach { - case fpe: FlatPathElement => - if (relevantPCsMap.contains(fpe.pc)) { - elements.append(fpe.copy) - } - case npe: NestedPathElement => - val leanedSubPath = makeLeanPathAcc(npe, relevantPCsMap) - val keepAlternativeBranches = toProcess.elementType match { - case Some(NestedPathType.CondWithAlternative) => true - case _ => false - } - if (leanedSubPath.isDefined) { - elements.append(leanedSubPath.get) - } else if (keepAlternativeBranches) { - elements.append(NestedPathElement(Seq.empty, None)) - } - case e => throw new IllegalStateException(s"Unexpected sub path element found: $e") - } - - if (elements.nonEmpty) { - Some(NestedPathElement(elements.toSeq, 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`. @@ -201,11 +83,6 @@ case class Path(elements: List[SubPath]) { * in the resulting lean path. `obj` should refer to a use site, most likely corresponding to an * (implicit) `toString` call. * - * @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`. - * * @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. */ @@ -231,58 +108,10 @@ case class Path(elements: List[SubPath]) { val endSite = obj.definedBy.toArray.max elements.foreach { - case fpe: FlatPathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => - leanPath.append(fpe) - case npe: NestedPathElement => - val leanedPath = makeLeanPathAcc(npe, pcMap) - if (leanedPath.isDefined) { - leanPath.append(leanedPath.get) - } - case _ => - } - - // 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) TODO check loops again what is with loops that are never executed? - if (leanPath.tail.isEmpty) { // TODO this throws if lean path is only one element long - leanPath.head match { - case npe: NestedPathElement if npe.element.tail.isEmpty => - leanPath.clear() - leanPath.appendAll(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 => - val newLast = stripUnnecessaryBranches(npe, endSite) - leanPath.remove(leanPath.size - 1) - leanPath.append(newLast) - case _ => - } + case fpe: FlatPathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => leanPath.append(fpe) + case _ => } Path(leanPath.toList) } } - -object Path { - - /** - * Returns the very last [[FlatPathElement]] in this path, respecting any nesting structure. If no last element - * exists, [[FlatPathElement.invalid]] is returned. - */ - @tailrec def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { - npe.element.lastOption match { - case Some(fpe: FlatPathElement) => fpe - case Some(npe: NestedPathElement) => - npe.element.last match { - case fpe: FlatPathElement => fpe - case innerNpe: NestedPathElement => getLastElementInNPE(innerNpe) - case _ => FlatPathElement.invalid - } - case _ => FlatPathElement.invalid - } - } -} 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 7642dee25f..08d7c6491b 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 @@ -9,8 +9,8 @@ package preprocessing import org.opalj.br.fpcf.properties.StringConstancyProperty 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.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string_definition.StringTreeNode import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.fpcf.PropertyStore @@ -24,9 +24,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ object PathTransformer { - /** - * Accumulator function for transforming a path into a StringTree element. - */ private def pathToTreeAcc(subpath: SubPath)(implicit state: ComputationState, ps: PropertyStore @@ -41,67 +38,27 @@ object PathTransformer { case _ => StringConstancyInformation.lb } Option.unless(sci.isTheNeutralElement)(sci.tree) - case npe: NestedPathElement => - if (npe.elementType.isDefined) { - npe.elementType.get match { - case _ => - val processedSubPaths = npe.element.flatMap { ne => pathToTreeAcc(ne) } - if (processedSubPaths.nonEmpty) { - npe.elementType.get match { - case NestedPathType.CondWithAlternative => - if (npe.element.size == processedSubPaths.size) { - Some(StringTreeOr(processedSubPaths)) - } else { - Some(StringTreeCond(StringTreeOr(processedSubPaths))) - } - case _ => None - } - } else { - None - } - } - } else { - npe.element.size match { - case 0 => None - case 1 => pathToTreeAcc(npe.element.head) - case _ => - val processed = npe.element.flatMap { ne => pathToTreeAcc(ne) } - if (processed.isEmpty) { - None - } else { - Some(StringTreeConcat(processed)) - } - } - } case _ => None } } - /** - * Takes a [[Path]] and transforms it into a [[StringTreeNode]]. 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. - * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed - * [[StringTreeNode]] 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)(implicit + def pathsToStringTree(paths: Seq[Path])(implicit state: ComputationState, ps: PropertyStore ): StringTreeNode = { - path.elements.size match { - case 1 => - pathToTreeAcc(path.elements.head).getOrElse(StringTreeDynamicString) - case _ => - val children = path.elements.flatMap { pathToTreeAcc(_) } - if (children.size == 1) { - // The concatenation of one child is the child itself - children.head - } else { - StringTreeConcat(children) + if (paths.isEmpty) { + StringTreeNeutralElement + } else { + val nodes = paths.map { path => + path.elements.size match { + case 1 => + pathToTreeAcc(path.elements.head).getOrElse(StringTreeDynamicString) + case _ => + StringTreeConcat(path.elements.flatMap(pathToTreeAcc(_))) } + } + + StringTreeOr(nodes) } } } From a49c1fda6d17004cf02ebf5e55be179e9988ed5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 2 May 2024 19:04:09 +0200 Subject: [PATCH 399/583] Simplify lean path construction --- .../analyses/string_analysis/StringAnalysis.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 762b28bb31..c40ec673f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -191,15 +191,12 @@ trait StringAnalysis extends FPCFAnalysis { private def computeLeanPaths(value: V)(implicit tac: TAC): Seq[Path] = { val defSites = value.definedBy.toArray.sorted - if (defSites.head < 0) { - computeLeanPathsForStringConst(value)(tac.stmts) + + val call = tac.stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + computeLeanPathsForStringBuilder(value) } else { - val call = tac.stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - computeLeanPathsForStringBuilder(value) - } else { - computeLeanPathsForStringConst(value)(tac.stmts) - } + computeLeanPathsForStringConst(value)(tac.stmts) } } From 8f7fa64aff9c6e0789fe52f30e083c08a0f6d34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 2 May 2024 19:04:31 +0200 Subject: [PATCH 400/583] Add simple form of structural analysis --- .../preprocessing/StructuralAnalysis.scala | 249 ++++++++++++++++++ build.sbt | 3 +- project/Dependencies.scala | 5 + 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala new file mode 100644 index 0000000000..7a554b0f58 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala @@ -0,0 +1,249 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string_analysis +package preprocessing + +import scala.collection.mutable + +import org.opalj.br.cfg.CFG + +import scalax.collection.edges.DiEdge +import scalax.collection.immutable.Graph +import scalax.collection.io.dot._ + +trait RegionType extends Product + +trait AcyclicRegionType extends RegionType +trait CyclicRegionType extends RegionType + +case object Block extends AcyclicRegionType +case object IfThen extends AcyclicRegionType +case object IfThenElse extends AcyclicRegionType +case object Case extends AcyclicRegionType +case object Proper extends AcyclicRegionType +case object SelfLoop extends CyclicRegionType +case object WhileLoop extends CyclicRegionType +case object NaturalLoop extends CyclicRegionType +case object Improper extends CyclicRegionType + +case class Region(regionType: RegionType, nodeIds: Set[Int]) { + + override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.mkString(",")})" +} + +/** + * @author Maximilian Rüsch + */ +class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { + type SGraph = Graph[Region, DiEdge[Region]] + type ControlTree = Graph[Region, DiEdge[Region]] + + val graph: SGraph = { + val edges = cfg.allNodes.flatMap(n => + n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) + ).toSet + var g = Graph.from(edges) + + g = g.incl(DiEdge(Region(Block, Set(cfg.normalReturnNode.nodeId)), Region(Block, Set(-42)))) + g = g.incl(DiEdge(Region(Block, Set(cfg.abnormalReturnNode.nodeId)), Region(Block, Set(-42)))) + + g + } + val entry: Region = Region(Block, Set(cfg.startBlock.nodeId)) + + def graphDot(graph: Graph[Region, DiEdge[Region]]): String = { + val root = DotRootGraph( + directed = true, + id = Some(Id("MyDot")), + attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr(Id("shape"), Id("record"))))), + attrList = List(DotAttr(Id("attr_1"), Id(""""one"""")), DotAttr(Id("attr_2"), Id(""))) + ) + + def edgeTransformer(innerEdge: SGraph#EdgeT): Option[(DotGraph, DotEdgeStmt)] = { + val edge = innerEdge.outer + Some( + ( + root, + DotEdgeStmt(NodeId(edge.source.toString), NodeId(edge.target.toString)) + ) + ) + } + + graph.toDot(root, edgeTransformer) + } + + def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { + assert(graph.isAcyclic, "The passed graph must not be cyclic!") + + var g = graph + var curEntry = entry + var controlTree = Graph.empty[Region, DiEdge[Region]] + + var outerIterations = 0 + while (g.order > 1 && outerIterations < 100) { + // Find post order depth first traversal order for nodes + var postCtr = 1 + val post = mutable.ListBuffer.empty[Region] + + def replace(g: SGraph, subRegions: Set[Region], regionType: RegionType): (SGraph, Region) = { + val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) + + var newGraph: SGraph = g + + // Compact + newGraph = newGraph.incl(newRegion) + val maxPost = post.indexOf(subRegions.maxBy(post.indexOf)) + post(maxPost) = newRegion + // Removing old regions from the graph is done later + post.filterInPlace(r => !subRegions.contains(r)) + postCtr = post.indexOf(newRegion) + + // Replace edges + for { + e <- newGraph.edges + } { + val source: Region = e.outer.source + val target: Region = e.outer.target + + if (!subRegions.contains(source) && subRegions.contains(target) && source != newRegion) { + newGraph += DiEdge(source, newRegion) + } else if (subRegions.contains(source) && !subRegions.contains(target) && target != newRegion) { + newGraph += DiEdge(newRegion, target) + } + } + newGraph = newGraph.removedAll(subRegions, Set.empty) + + (newGraph, newRegion) + } + + PostOrderTraversal.foreachInTraversalFrom[Region, SGraph](g, curEntry)(post.append) + + while (g.order > 1 && postCtr < post.size) { + var n = post(postCtr) + + val (newStartingNode, acyclicRegionOpt) = AcyclicRegionType.locate(g, n) + n = newStartingNode + if (acyclicRegionOpt.isDefined) { + val (arType, nodes) = acyclicRegionOpt.get + + val (newGraph, newRegion) = replace(g, nodes, arType) + g = newGraph + for { + node <- nodes + } { + controlTree = controlTree.incl(DiEdge(newRegion, node)) + } + + if (nodes.contains(curEntry)) { + curEntry = newRegion + } + } else { + // Detect cyclic region + postCtr += 1 + } + } + + outerIterations += 1 + } + + (g, controlTree) + } +} + +object PostOrderTraversal { + + private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( + graph: G, + toVisit: Seq[A], + visited: Set[A] + )(nodeHandler: A => Unit): Unit = { + if (toVisit.nonEmpty) { + val next = toVisit.head + // TODO stabilize traversal + val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toSeq + + foreachInTraversal(graph, nextSuccessors ++ toVisit.tail, visited + next)(nodeHandler) + nodeHandler(next) + } + } + + def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit): Unit = + foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) +} + +object AcyclicRegionType { + def locate[A, G <: Graph[A, DiEdge[A]]](graph: G, startingNode: A): (A, Option[(AcyclicRegionType, Set[A])]) = { + var nSet = Set.empty[A] + + // Expand nSet down + var n = startingNode + var p = true + var s = graph.get(n).diSuccessors.size == 1 // TODO refactor into own node type and `hasSingleSuccessor` / `getSingleSuccessor once running + while (p & s) { + nSet += n + n = graph.get(n).diSuccessors.head.outer + p = graph.get(n).diPredecessors.size == 1 + s = graph.get(n).diSuccessors.size == 1 + } + if (p) { + nSet += n + } + + // Expand nSet up + n = startingNode + p = graph.get(n).diPredecessors.size == 1 + s = true + while (p & s) { + nSet += n + n = graph.get(n).diPredecessors.head.outer + p = graph.get(n).diPredecessors.size == 1 + s = graph.get(n).diSuccessors.size == 1 + } + if (s) { + nSet += n + } + + val newStartingNode = n + val newDirectSuccessors = graph.get(newStartingNode).diSuccessors.map(_.outer) + val rType = if (nSet.size >= 2) { + Some(Block) + } else if (newDirectSuccessors.size == 2) { + val m = newDirectSuccessors.head + val k = newDirectSuccessors.tail.head + if (graph.get(m).diSuccessors.headOption == graph.get(k).diSuccessors.headOption + && graph.get(m).diSuccessors.size == 1 + && graph.get(m).diPredecessors.size == 1 + && graph.get(k).diPredecessors.size == 1 + ) { + nSet = Set(newStartingNode, m, k) + Some(IfThenElse) + } else if (( + graph.get(m).diSuccessors.size == 1 + && graph.get(m).diSuccessors.head.outer == k + && graph.get(m).diPredecessors.size == 1 + && graph.get(k).diPredecessors.size == 2 + ) || ( + graph.get(k).diSuccessors.size == 1 + && graph.get(k).diSuccessors.head.outer == m + && graph.get(k).diPredecessors.size == 1 + && graph.get(m).diPredecessors.size == 2 + ) + ) { + nSet = Set(newStartingNode, m, k) + Some(IfThen) + } else { + None // TODO add method "hasCycles" somehow for proper + } + } else if (newDirectSuccessors.size > 2) { + // TODO implement Case + None + } else { + None // TODO add method "hasCycles" somehow for proper + } + + (newStartingNode, rType.map((_, nSet))) + } +} diff --git a/build.sbt b/build.sbt index 4e5e85a927..032e90cd42 100644 --- a/build.sbt +++ b/build.sbt @@ -319,7 +319,8 @@ lazy val `ThreeAddressCode` = (project in file("OPAL/tac")) .title("OPAL - Three Address Code") ++ Seq("-groups", "-implicits")), assembly / assemblyJarName := "OPALTACDisassembler.jar", assembly / mainClass := Some("org.opalj.tac.TAC"), - run / fork := true + run / fork := true, + libraryDependencies ++= Dependencies.tac ) .dependsOn(ai % "it->it;it->test;test->test;compile->compile") .dependsOn(ifds % "it->it;it->test;test->test;compile->compile") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index be2ea989e3..e76de5acba 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -25,6 +25,8 @@ object Dependencies { val jacksonDF = "2.12.2" val fastutil = "8.5.4" val apkparser = "2.6.10" + val scalagraphcore = "2.0.1" + val scalagraphdot = "2.0.0" val openjfx = "16" @@ -56,6 +58,8 @@ object Dependencies { val fastutil = "it.unimi.dsi" % "fastutil" % version.fastutil withSources () withJavadoc () val javafxBase = "org.openjfx" % "javafx-base" % version.openjfx classifier osName val apkparser = "net.dongliu" % "apk-parser" % version.apkparser + val scalagraphcore = "org.scala-graph" %% "graph-core" % version.scalagraphcore + val scalagraphdot = "org.scala-graph" %% "graph-dot" % version.scalagraphdot val javacpp = "org.bytedeco" % "javacpp" % version.javacpp val javacpp_llvm = "org.bytedeco" % "llvm-platform" % (version.javacpp_llvm + "-" + version.javacpp) @@ -76,6 +80,7 @@ object Dependencies { val si = Seq() val bi = Seq(commonstext) val br = Seq(scalaparsercombinators, scalaxml) + val tac = Seq(scalagraphcore, scalagraphdot) val ifds = Seq() val tools = Seq(txtmark, jacksonDF) val hermes = Seq(txtmark, jacksonDF, javafxBase) From d30fefa30c508d2e451ed0c213227d07df343496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 2 May 2024 19:59:09 +0200 Subject: [PATCH 401/583] Implement location of proper acyclic intervals --- .../preprocessing/StructuralAnalysis.scala | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala index 7a554b0f58..a3f9742219 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala @@ -31,7 +31,7 @@ case object Improper extends CyclicRegionType case class Region(regionType: RegionType, nodeIds: Set[Int]) { - override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.mkString(",")})" + override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")})" } /** @@ -72,7 +72,17 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { ) } - graph.toDot(root, edgeTransformer) + def iNodeTransformer(innerNode: SGraph#NodeT): Option[(DotGraph, DotNodeStmt)] = { + val node = innerNode.outer + Some( + ( + root, + DotNodeStmt(NodeId(node.toString)) + ) + ) + } + + graph.toDot(root, edgeTransformer, iNodeTransformer = Some(iNodeTransformer)) } def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { @@ -208,7 +218,33 @@ object AcyclicRegionType { val newStartingNode = n val newDirectSuccessors = graph.get(newStartingNode).diSuccessors.map(_.outer) - val rType = if (nSet.size >= 2) { + + def locateProperAcyclicInterval: Option[AcyclicRegionType] = { + assert(newDirectSuccessors.size > 1, "Detection for single direct successors should have already run!") + + var currentNodeSet = Set(n) + var currentSuccessors = graph.get(n).diSuccessors.map(_.outer) + while (currentSuccessors.size > 1 && graph.filter(nodeP = + node => currentNodeSet.contains(node.outer) + ).isAcyclic + ) { + currentNodeSet = currentNodeSet ++ currentSuccessors + currentSuccessors = currentSuccessors.flatMap(node => graph.get(node).diSuccessors.map(_.outer)) + } + + val allPredecessors = currentNodeSet.excl(n).flatMap(node => graph.get(node).diPredecessors.map(_.outer)) + if (graph.filter(nodeP = node => currentNodeSet.contains(node.outer)).isCyclic) { + None + } else if (!allPredecessors.equals(currentNodeSet.diff(currentSuccessors))) { + None + } else { + nSet = currentNodeSet ++ currentSuccessors + + Some(Proper) + } + } + + val rType = if (nSet.size > 1) { Some(Block) } else if (newDirectSuccessors.size == 2) { val m = newDirectSuccessors.head @@ -235,13 +271,13 @@ object AcyclicRegionType { nSet = Set(newStartingNode, m, k) Some(IfThen) } else { - None // TODO add method "hasCycles" somehow for proper + locateProperAcyclicInterval } } else if (newDirectSuccessors.size > 2) { - // TODO implement Case - None + // TODO implement Case as well + locateProperAcyclicInterval } else { - None // TODO add method "hasCycles" somehow for proper + None } (newStartingNode, rType.map((_, nSet))) From cc52286ca3be88551d99aff16aed8f3feaf50aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 3 May 2024 10:21:09 +0200 Subject: [PATCH 402/583] Reimagine def site entities as def use site entities --- .../StringAnalysisMatcher.scala | 2 +- .../string_analysis/ComputationState.scala | 4 +-- .../string_analysis/EPSDepender.scala | 2 +- .../string_analysis/StringAnalysis.scala | 8 ++--- .../string_analysis/StringInterpreter.scala | 12 +++---- .../BinaryExprInterpreter.scala | 2 +- .../InterpretationHandler.scala | 24 ++++++------- .../SimpleValueConstExprInterpreter.scala | 2 +- .../L0ArrayAccessInterpreter.scala | 6 ++-- .../L0FunctionCallInterpreter.scala | 16 ++++----- .../L0InterpretationHandler.scala | 4 ++- .../L0NewArrayInterpreter.scala | 6 ++-- .../L0NonVirtualFunctionCallInterpreter.scala | 2 +- .../L0NonVirtualMethodCallInterpreter.scala | 8 ++--- .../L0StaticFunctionCallInterpreter.scala | 6 ++-- .../L0VirtualFunctionCallInterpreter.scala | 36 +++++++++---------- .../L0VirtualMethodCallInterpreter.scala | 2 +- .../L1FieldReadInterpreter.scala | 4 +-- .../L1InterpretationHandler.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 4 +-- .../preprocessing/PathTransformer.scala | 2 +- .../string_analysis/string_analysis.scala | 2 +- 22 files changed, 79 insertions(+), 77 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index be99fbe38a..13791cdc55 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -72,7 +72,7 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { val (actLevel, actString) = properties.head match { case prop: StringConstancyProperty => val sci = prop.stringConstancyInformation - (sci.constancyLevel.toString.toLowerCase, sci.tree.toRegex) + (sci.constancyLevel.toString.toLowerCase, sci.toRegex) case _ => ("", "") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala index fb1e42a9b6..01294d69ad 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala @@ -35,7 +35,7 @@ case class ComputationState(dm: DefinedMethod, entity: SContext) { /** * If not empty, this routine can only produce an intermediate result */ - var dependees: List[EOptionP[DefSiteEntity, Property]] = List() + var dependees: List[EOptionP[DUSiteEntity, Property]] = List() } -case class DefSiteState(pc: Int, dm: DefinedMethod, tac: TAC) +case class DUSiteState(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala index 7908f40f01..15241f00a2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala @@ -13,7 +13,7 @@ import org.opalj.fpcf.SomeEOptionP private[string_analysis] case class EPSDepender[T <: ASTNode[V]]( instr: T, pc: Int, - state: DefSiteState, + state: DUSiteState, dependees: Seq[SomeEOptionP] ) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index c40ec673f6..060f5097d8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -72,7 +72,7 @@ trait StringAnalysis extends FPCFAnalysis { // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val ep = ps( - InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac), + InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac, state.entity._1), StringConstancyProperty.key ) if (ep.isRefinable) { @@ -98,7 +98,7 @@ trait StringAnalysis extends FPCFAnalysis { state.computedLeanPaths.flatMap(getPCsInPath).distinct.foreach { pc => propertyStore( - InterpretationHandler.getEntityForPC(pc, state.dm, state.tac), + InterpretationHandler.getEntityForPC(pc, state.dm, tac, state.entity._1), StringConstancyProperty.key ) match { case FinalEP(e, _) => @@ -134,7 +134,7 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) determinePossibleStrings(state) - case FinalEP(e: DefSiteEntity, _) if eps.pk.equals(StringConstancyProperty.key) => + case FinalEP(e: DUSiteEntity, _) if eps.pk.equals(StringConstancyProperty.key) => state.dependees = state.dependees.filter(_.e != e) // No more dependees => Return the result for this analysis run @@ -301,7 +301,7 @@ trait LazyStringAnalysis (e: Entity) => { e match { case _: (_, _) => initData._1.analyze(e.asInstanceOf[SContext]) - case entity: DefSiteEntity => initData._2.analyze(entity) + case entity: DUSiteEntity => initData._2.analyze(entity) case _ => throw new IllegalArgumentException(s"Unexpected entity passed for string analysis: $e") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 9e7e25949e..5eeda30141 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -31,9 +31,9 @@ trait StringInterpreter { * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given * instruction. */ - def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult + def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DefSiteState): Result = + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DUSiteState): Result = StringInterpreter.computeFinalResult(pc, sci) // IMPROVE remove this since awaiting all final is not really feasible @@ -71,7 +71,7 @@ trait StringInterpreter { object StringInterpreter { - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DefSiteState): Result = + def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DUSiteState): Result = Result(FinalEP( InterpretationHandler.getEntityForPC(pc), StringConstancyProperty(sci.copy(tree = sci.tree.simplify)) @@ -82,7 +82,7 @@ trait ParameterEvaluatingStringInterpreter extends StringInterpreter { val ps: PropertyStore - protected def getParametersForPC(pc: Int)(implicit state: DefSiteState): Seq[Expr[V]] = { + protected def getParametersForPC(pc: Int)(implicit state: DUSiteState): Seq[Expr[V]] = { state.tac.stmts(state.tac.pcToIndex(pc)) match { case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params case Assignment(_, _, fc: FunctionCall[V]) => fc.params @@ -91,8 +91,8 @@ trait ParameterEvaluatingStringInterpreter extends StringInterpreter { } protected def evaluateParameters(params: Seq[Expr[V]])(implicit - state: DefSiteState - ): Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = { + state: DUSiteState + ): Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = { params.map { nextParam => Seq.from(nextParam.asVar.definedBy.toArray.sorted.map { ds => ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) 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 de570a278a..95f53357d7 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 @@ -26,7 +26,7 @@ object BinaryExprInterpreter extends StringInterpreter { * * For all other expressions, a [[StringConstancyInformation.neutralElement]] will be returned. */ - def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val sci = instr.cTpe match { case ComputationalTypeInt => StringConstancyInformation.dynamicInt case ComputationalTypeFloat => StringConstancyInformation.dynamicFloat 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 8a4eff3454..f4a826f3e4 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,9 @@ import org.opalj.fpcf.ProperPropertyComputationResult */ abstract class InterpretationHandler { - def analyze(entity: DefSiteEntity): ProperPropertyComputationResult = { + def analyze(entity: DUSiteEntity): ProperPropertyComputationResult = { val pc = entity.pc - implicit val defSiteState: DefSiteState = DefSiteState(pc, entity.dm, entity.tac) + implicit val defSiteState: DUSiteState = DUSiteState(pc, entity.dm, entity.tac, entity.entity) if (pc <= FormalParametersOriginOffset) { if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) @@ -43,7 +43,7 @@ abstract class InterpretationHandler { processNewDefSitePC(pc) } - protected def processNewDefSitePC(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult + protected def processNewDefSitePC(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult } object InterpretationHandler { @@ -202,18 +202,18 @@ object InterpretationHandler { def getStringConstancyInformationForReplace: StringConstancyInformation = StringConstancyInformation(StringConstancyType.REPLACE, StringTreeDynamicString) - def getEntityForDefSite(defSite: Int)(implicit state: DefSiteState): DefSiteEntity = + def getEntityForDefSite(defSite: Int)(implicit state: DUSiteState): DUSiteEntity = getEntityForPC(pcOfDefSite(defSite)(state.tac.stmts)) - def getEntityForDefSite(defSite: Int, dm: DefinedMethod, tac: TAC): DefSiteEntity = - getEntityForPC(pcOfDefSite(defSite)(tac.stmts), dm, tac) + def getEntityForDefSite(defSite: Int, dm: DefinedMethod, tac: TAC, entity: SEntity): DUSiteEntity = + getEntityForPC(pcOfDefSite(defSite)(tac.stmts), dm, tac, entity) - def getEntityForPC(pc: Int)(implicit state: DefSiteState): DefSiteEntity = - getEntityForPC(pc, state.dm, state.tac) + def getEntityForPC(pc: Int)(implicit state: DUSiteState): DUSiteEntity = + getEntityForPC(pc, state.dm, state.tac, state.entity) - def getEntity(state: DefSiteState): DefSiteEntity = - getEntityForPC(state.pc, state.dm, state.tac) + def getEntity(state: DUSiteState): DUSiteEntity = + getEntityForPC(state.pc, state.dm, state.tac, state.entity) - def getEntityForPC(pc: Int, dm: DefinedMethod, tac: TAC): DefSiteEntity = - DefSiteEntity(pc, dm, tac) + def getEntityForPC(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity): DUSiteEntity = + DUSiteEntity(pc, dm, tac, entity) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index f6e3ca42cb..b6dc3b554b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -18,7 +18,7 @@ object SimpleValueConstExprInterpreter extends StringInterpreter { override type T = SimpleValueConst - def interpret(expr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + def interpret(expr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val treeOpt = expr match { case ic: IntConst => Some(StringTreeConst(ic.value.toString)) case fc: FloatConst => Some(StringTreeConst(fc.value.toString)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala index 2460d43b86..1d1c965b77 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -26,7 +26,7 @@ case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter override type T = ArrayLoad[V] - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val defSitePCs = getStoreAndLoadDefSitePCs(instr)(state.tac.stmts) val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.key) } @@ -41,12 +41,12 @@ case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter ) ) } else { - finalResult(pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(results.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) } } private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { var resultSci = StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].sci diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala index a1edcf6341..64cc64b32e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala @@ -34,7 +34,7 @@ trait L0FunctionCallInterpreter implicit val ps: PropertyStore protected[this] case class FunctionCallState( - state: DefSiteState, + state: DUSiteState, calleeMethods: Seq[Method], var tacDependees: Map[Method, EOptionP[Method, TACAI]], var returnDependees: Map[Method, Seq[EOptionP[SContext, StringConstancyProperty]]] = Map.empty @@ -43,12 +43,12 @@ trait L0FunctionCallInterpreter var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) - private var _paramDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = Seq.empty - private var _paramEntityToPositionMapping: Map[DefSiteEntity, (Int, Int)] = Map.empty + private var _paramDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = Seq.empty + private var _paramEntityToPositionMapping: Map[DUSiteEntity, (Int, Int)] = Map.empty - def paramDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]] = _paramDependees + def paramDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = _paramDependees - def updateParamDependee(newDependee: EOptionP[DefSiteEntity, StringConstancyProperty]): Unit = { + def updateParamDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { val pos = _paramEntityToPositionMapping(newDependee.e) _paramDependees = _paramDependees.updated( pos._1, @@ -59,7 +59,7 @@ trait L0FunctionCallInterpreter ) } - def setParamDependees(newParamDependees: Seq[Seq[EOptionP[DefSiteEntity, StringConstancyProperty]]]): Unit = { + def setParamDependees(newParamDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]]): Unit = { _paramDependees = newParamDependees _paramEntityToPositionMapping = Map.empty _paramDependees.zipWithIndex.map { @@ -163,8 +163,8 @@ trait L0FunctionCallInterpreter callState.updateReturnDependee(contextEPS.e._2, contextEPS) tryComputeFinalResult(callState) - case EUBP(_: DefSiteEntity, _: StringConstancyProperty) => - callState.updateParamDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) + case EUBP(_: DUSiteEntity, _: StringConstancyProperty) => + callState.updateParamDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) tryComputeFinalResult(callState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index 15effb10c8..cf6e42f152 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -26,7 +26,9 @@ class L0InterpretationHandler()( ps: PropertyStore ) extends InterpretationHandler { - override protected def processNewDefSitePC(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override protected def processNewDefSitePC(pc: Int)(implicit + state: DUSiteState + ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala index 1b09084e0a..6184a0c676 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala @@ -24,7 +24,7 @@ case class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { override type T = NewArray[V] - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { if (instr.counts.length != 1) { // Only supports 1-D arrays return computeFinalResult(pc, StringConstancyInformation.lb) @@ -56,11 +56,11 @@ case class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { ) ) } else { - finalResult(pc)(allResults.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(pc)(allResults.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) } } - private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: DefSiteState): Result = { + private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: DUSiteState): Result = { val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) val sci = if (resultsScis.forall(_.isTheNeutralElement)) { // It might be that there are no results; in such a case, set the string information to the lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 5c6a9cd082..0b3eebf5e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -26,7 +26,7 @@ case class L0NonVirtualFunctionCallInterpreter()( override type T = NonVirtualFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { return computeFinalResult(pc, StringConstancyInformation.lb) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 22eebb499a..d3bcd8f01c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -24,14 +24,14 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringIn override type T = NonVirtualMethodCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr, pc) case _ => computeFinalResult(pc, StringConstancyInformation.neutralElement) } } - private def interpretInit(init: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + private def interpretInit(init: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { init.params.size match { case 0 => computeFinalResult(pc, StringConstancyInformation.neutralElement) case _ => @@ -40,7 +40,7 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringIn ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) } if (results.forall(_.isFinal)) { - finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]]) + finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) } else { InterimResult.forUB( InterpretationHandler.getEntityForPC(pc), @@ -55,7 +55,7 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringIn } } - private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit state: DefSiteState): Result = + private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit state: DUSiteState): Result = computeFinalResult( pc, StringConstancyInformation.reduceMultiple(results.map { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 3f00d4b5a5..dd9ff9a813 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -33,7 +33,7 @@ case class L0StaticFunctionCallInterpreter()( override type T = StaticFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { instr.name match { case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, pc) case _ => interpretArbitraryCall(instr, pc) @@ -50,7 +50,7 @@ private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter override type T = StaticFunctionCall[V] def interpretArbitraryCall(instr: T, pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { @@ -71,7 +71,7 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends St val ps: PropertyStore - def processStringValueOf(call: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + def processStringValueOf(call: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { def finalResult(results: Seq[SomeFinalEP]): Result = { // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 2be55dc276..19b7c91c7b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -68,7 +68,7 @@ case class L0VirtualFunctionCallInterpreter( * * If none of the above-described cases match, a [[NoResult]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { instr.name match { case "append" => interpretAppendCall(instr, pc) case "toString" => interpretToStringCall(instr, pc) @@ -92,7 +92,7 @@ case class L0VirtualFunctionCallInterpreter( * the given `toString` is such a function call! Otherwise, the expected behavior cannot be guaranteed. */ private def interpretToStringCall(call: T, pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { eps match { @@ -128,14 +128,14 @@ case class L0VirtualFunctionCallInterpreter( /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = + private def interpretReplaceCall(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = computeFinalResult(pc, InterpretationHandler.getStringConstancyInformationForReplace) } private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { protected def interpretArbitraryCall(call: T, pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = computeFinalResult(pc, StringConstancyInformation.lb) } @@ -150,15 +150,15 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter val ps: PropertyStore private[this] case class AppendCallState( - appendCall: T, - param: V, - defSitePC: Int, - state: DefSiteState, - var receiverDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]], - var valueDependees: Seq[EOptionP[DefSiteEntity, StringConstancyProperty]] + appendCall: T, + param: V, + defSitePC: Int, + state: DUSiteState, + var receiverDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]], + var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] ) { - def updateDependee(newDependee: EOptionP[DefSiteEntity, StringConstancyProperty]): Unit = { + def updateDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { if (receiverDependees.exists(_.e == newDependee.e)) { receiverDependees = receiverDependees.updated( receiverDependees.indexWhere(_.e == newDependee.e), @@ -175,12 +175,12 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter def hasDependees: Boolean = receiverDependees.exists(_.isRefinable) || valueDependees.exists(_.isRefinable) - def dependees: Iterable[EOptionP[DefSiteEntity, StringConstancyProperty]] = + def dependees: Iterable[EOptionP[DUSiteEntity, StringConstancyProperty]] = receiverDependees.filter(_.isRefinable) ++ valueDependees.filter(_.isRefinable) } def interpretAppendCall(appendCall: T, pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { // Get receiver results val receiverResults = appendCall.receiver.asVar.definedBy.toList.sorted.map { ds => @@ -206,7 +206,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter private def continuation(appendState: AppendCallState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => - appendState.updateDependee(eps.asInstanceOf[EOptionP[DefSiteEntity, StringConstancyProperty]]) + appendState.updateDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) tryComputeFinalAppendCallResult(appendState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") @@ -228,7 +228,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter _.asFinal.p.sci }) val valueSci = transformAppendValueResult( - appendState.valueDependees.asInstanceOf[Seq[FinalEP[DefSiteEntity, StringConstancyProperty]]] + appendState.valueDependees.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]] ) computeFinalResult( @@ -242,7 +242,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter } private def transformAppendValueResult( - results: Seq[FinalEP[DefSiteEntity, StringConstancyProperty]] + results: Seq[FinalEP[DUSiteEntity, StringConstancyProperty]] )(implicit appendState: AppendCallState): StringConstancyInformation = { val sciValues = results.map(_.p.sci) val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) @@ -285,7 +285,7 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre val ps: PropertyStore def interpretSubstringCall(substringCall: T, pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) @@ -308,7 +308,7 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( results: Seq[SomeFinalEP] - )(implicit state: DefSiteState): Result = { + )(implicit state: DUSiteState): Result = { val receiverSci = StringConstancyInformation.reduceMultiple(results.map { _.p.asInstanceOf[StringConstancyProperty].sci }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 4cd110c21e..c7923e1ab1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -31,7 +31,7 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { * * For all other calls, a [[StringConstancyInformation.neutralElement]] will be returned. */ - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength case "setLength" => StringConstancyInformation(StringConstancyType.RESET) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index df7becf13f..203ba0d1e9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -72,7 +72,7 @@ case class L1FieldReadInterpreter( private case class FieldReadState( defSitePC: Int, - state: DefSiteState, + state: DUSiteState, var hasInit: Boolean = false, var hasUnresolvableAccess: Boolean = false, var accessDependees: Seq[EOptionP[SContext, StringConstancyProperty]] = Seq.empty @@ -97,7 +97,7 @@ case class L1FieldReadInterpreter( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, pc: Int)(implicit state: DefSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { // 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 analysis could then use that analysis. if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index 9a6a46b62c..a974e169f3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -43,7 +43,7 @@ class L1InterpretationHandler( implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) override protected def processNewDefSitePC(pc: Int)(implicit - state: DefSiteState + state: DUSiteState ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 68fe7deaa2..f9d58f520b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -50,7 +50,7 @@ class L1VirtualFunctionCallInterpreter( ) override protected def interpretArbitraryCall(instr: T, pc: Int)( - implicit state: DefSiteState + implicit state: DUSiteState ): ProperPropertyComputationResult = { val depender = CalleeDepender(pc, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) @@ -78,7 +78,7 @@ class L1VirtualFunctionCallInterpreter( } private def continuation( - state: DefSiteState, + state: DUSiteState, depender: CalleeDepender )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { 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 08d7c6491b..a71baf4b18 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 @@ -31,7 +31,7 @@ object PathTransformer { subpath match { case fpe: FlatPathElement => val sci = ps( - InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac), + InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac, state.entity._1), StringConstancyProperty.key ) match { case UBP(scp) => scp.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 b5d9dfe26e..74080a257a 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 @@ -30,5 +30,5 @@ package object string_analysis { * The entity used for requesting string constancy information for specific def sites of an entity. The def site * should be given as a [[org.opalj.br.PC]]. */ - case class DefSiteEntity(pc: Int, dm: DefinedMethod, tac: TAC) + case class DUSiteEntity(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) } From 84caf223fe19a6566174559c94ac306dfb5902d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 3 May 2024 10:32:06 +0200 Subject: [PATCH 403/583] Simplify path handling --- .../string_analysis/StringAnalysis.scala | 24 ++++--------- .../L0VirtualFunctionCallInterpreter.scala | 12 +++---- .../string_analysis/preprocessing/Path.scala | 36 +++++++------------ .../preprocessing/PathTransformer.scala | 20 +++++------ .../preprocessing/SimplePathFinder.scala | 2 +- 5 files changed, 33 insertions(+), 61 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 060f5097d8..059fc2949c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -26,11 +26,10 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath import org.opalj.tac.fpcf.properties.TACAI /** @@ -96,7 +95,7 @@ trait StringAnalysis extends FPCFAnalysis { state.computedLeanPaths = computeLeanPaths(uVar) } - state.computedLeanPaths.flatMap(getPCsInPath).distinct.foreach { pc => + state.computedLeanPaths.flatMap(_.elements.map(_.pc)).distinct.foreach { pc => propertyStore( InterpretationHandler.getEntityForPC(pc, state.dm, tac, state.entity._1), StringConstancyProperty.key @@ -154,7 +153,7 @@ trait StringAnalysis extends FPCFAnalysis { * its final result is not yet ready, however, this function finalizes, e.g., that load). * * @param state The final computation state. For this state the following criteria must apply: - * For each [[FlatPathElement]], there must be a corresponding entry in + * For each [[PathElement]], 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)! @@ -201,7 +200,7 @@ trait StringAnalysis extends FPCFAnalysis { } private def computeLeanPathsForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Seq[Path] = - value.definedBy.toList.sorted.map(ds => Path(List(FlatPathElement(ds)))) + value.definedBy.toList.sorted.map(ds => Path(List(PathElement(ds)))) private def computeLeanPathsForStringBuilder(value: V)(implicit tac: TAC): Seq[Path] = { val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) @@ -211,17 +210,6 @@ trait StringAnalysis extends FPCFAnalysis { Seq(SimplePathFinder.findPath(tac).makeLeanPath(value)) } } - - private def getPCsInPath(path: Path): Iterable[Int] = { - def getDefSitesOfPathAcc(subpath: SubPath): Iterable[Int] = { - subpath match { - case fpe: FlatPathElement => Seq(fpe.pc) - case _ => Seq.empty - } - } - - path.elements.flatMap(getDefSitesOfPathAcc) - } } object StringAnalysis { @@ -300,9 +288,9 @@ trait LazyStringAnalysis StringConstancyProperty.key, (e: Entity) => { e match { - case _: (_, _) => initData._1.analyze(e.asInstanceOf[SContext]) + case _: (_, _) => initData._1.analyze(e.asInstanceOf[SContext]) case entity: DUSiteEntity => initData._2.analyze(entity) - case _ => throw new IllegalArgumentException(s"Unexpected entity passed for string analysis: $e") + case _ => throw new IllegalArgumentException(s"Unexpected entity passed for string analysis: $e") } } ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 19b7c91c7b..c94286a20a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -150,12 +150,12 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter val ps: PropertyStore private[this] case class AppendCallState( - appendCall: T, - param: V, - defSitePC: Int, - state: DUSiteState, - var receiverDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]], - var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] + appendCall: T, + param: V, + defSitePC: Int, + state: DUSiteState, + var receiverDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]], + var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] ) { def updateDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { 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 7a04212b62..379ade9e3f 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 @@ -12,38 +12,26 @@ import scala.collection.mutable.ListBuffer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.value.ValueInformation -/** - * @author Maximilian Rüsch - */ - -/** - * [[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 with `pc` by its [[org.opalj.br.PC]]. + * + * @author Maximilian Rüsch */ -class FlatPathElement private[FlatPathElement] (val pc: Int) extends SubPath { +class PathElement private[PathElement] (val pc: Int) { def stmtIndex(implicit pcToIndex: Array[Int]): Int = valueOriginOfPC(pc, pcToIndex).get - def copy = new FlatPathElement(pc) + def copy = new PathElement(pc) } -object FlatPathElement extends SubPath { - def apply(defSite: Int)(implicit stmts: Array[Stmt[V]]) = new FlatPathElement(pcOfDefSite(defSite)) +object PathElement { + def apply(defSite: Int)(implicit stmts: Array[Stmt[V]]) = new PathElement(pcOfDefSite(defSite)) - def unapply(fpe: FlatPathElement)(implicit pcToIndex: Array[Int]): Some[Int] = Some(fpe.stmtIndex) + def unapply(fpe: PathElement)(implicit pcToIndex: Array[Int]): Some[Int] = Some(fpe.stmtIndex) - def fromPC(pc: Int) = new FlatPathElement(pc) + def fromPC(pc: Int) = new PathElement(pc) } -/** - * 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]) { +case class Path(elements: List[PathElement]) { /** * Takes an object of interest, `obj`, and a list of statements, `stmts` and finds all @@ -104,12 +92,12 @@ case class Path(elements: List[SubPath]) { case _ => true } }.map { s => (pcOfDefSite(s), ()) }.toMap - val leanPath = ListBuffer[SubPath]() + val leanPath = ListBuffer[PathElement]() val endSite = obj.definedBy.toArray.max elements.foreach { - case fpe: FlatPathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => leanPath.append(fpe) - case _ => + case fpe: PathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => leanPath.append(fpe) + case _ => } Path(leanPath.toList) 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 a71baf4b18..d8827c64fa 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 @@ -24,22 +24,18 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ object PathTransformer { - private def pathToTreeAcc(subpath: SubPath)(implicit + private def pathToTreeAcc(subpath: PathElement)(implicit state: ComputationState, ps: PropertyStore ): Option[StringTreeNode] = { - subpath match { - case fpe: FlatPathElement => - val sci = ps( - InterpretationHandler.getEntityForPC(fpe.pc, state.dm, state.tac, state.entity._1), - StringConstancyProperty.key - ) match { - case UBP(scp) => scp.sci - case _ => StringConstancyInformation.lb - } - Option.unless(sci.isTheNeutralElement)(sci.tree) - case _ => None + val sci = ps( + InterpretationHandler.getEntityForPC(subpath.pc, state.dm, state.tac, state.entity._1), + StringConstancyProperty.key + ) match { + case UBP(scp) => scp.sci + case _ => StringConstancyInformation.lb } + Option.unless(sci.isTheNeutralElement)(sci.tree) } def pathsToStringTree(paths: Seq[Path])(implicit diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala index fb1608a692..8e674f16cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala @@ -27,6 +27,6 @@ object SimplePathFinder { */ def findPath(tac: TAC): Path = { val cfg = tac.cfg - Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(FlatPathElement.fromPC).toList) + Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(PathElement.fromPC).toList) } } From 6075f1558cf84f6dccb96cd04afc6c18a08cbe2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 3 May 2024 11:01:00 +0200 Subject: [PATCH 404/583] Return functions for string constancy and introduce path finder hierarchy --- .../StringConstancyInformation.scala | 75 +++++++++-------- .../string_analysis/StringAnalysis.scala | 18 ++-- .../string_analysis/StringInterpreter.scala | 2 +- .../InterpretationHandler.scala | 42 ---------- .../SimpleValueConstExprInterpreter.scala | 19 ++--- .../string_analysis/l0/L0StringAnalysis.scala | 7 +- .../L0StaticFunctionCallInterpreter.scala | 13 ++- .../L0VirtualFunctionCallInterpreter.scala | 84 +++++++++++-------- .../L0VirtualMethodCallInterpreter.scala | 3 +- .../string_analysis/l1/L1StringAnalysis.scala | 7 +- .../L1FieldReadInterpreter.scala | 5 +- .../string_analysis/preprocessing/Path.scala | 81 +----------------- ...implePathFinder.scala => PathFinder.scala} | 19 +++-- 13 files changed, 141 insertions(+), 234 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/{SimplePathFinder.scala => PathFinder.scala} (58%) 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 9532b6c326..007d50b273 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 @@ -8,61 +8,64 @@ package string_definition /** * @author Maximilian Rüsch */ -case class StringConstancyInformation( - constancyType: StringConstancyType.Value = StringConstancyType.APPEND, - tree: StringTreeNode = StringTreeNeutralElement -) { +trait StringConstancyInformation { - def isTheNeutralElement: Boolean = - constancyLevel == StringConstancyLevel.CONSTANT && - constancyType == StringConstancyType.APPEND && - tree.isNeutralElement + def treeFn: StringTreeNode => StringTreeNode + def tree: StringTreeNode - def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel + def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation - def toRegex: String = tree.toRegex + final def isTheNeutralElement: Boolean = tree.isNeutralElement + final def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel + final def toRegex: String = tree.toRegex +} + +case class StringConstancyInformationConst(override val tree: StringTreeNode) extends StringConstancyInformation { + + override def treeFn: StringTreeNode => StringTreeNode = _ => tree - def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { - val newTree = tree.replaceParameters(parameters.map { + override def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { + StringConstancyInformationConst(tree.replaceParameters(parameters.map { case (index, sci) => (index, sci.tree) - }) + })) + } +} - StringConstancyInformation(constancyType, newTree) +case class StringConstancyInformationFunction(override val treeFn: StringTreeNode => StringTreeNode) + extends StringConstancyInformation { + + override def tree: StringTreeNode = treeFn(StringTreeNeutralElement) + + override def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { + StringConstancyInformationFunction((pv: StringTreeNode) => + treeFn(pv).replaceParameters(parameters.map { + case (index, sci) => (index, sci.tree) + }) + ) } } -/** - * Provides a collection of instance-independent but string-constancy related values. - */ object StringConstancyInformation { - /** - * 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: Seq[StringConstancyInformation]): StringConstancyInformation = { val relScis = scis.filter(!_.isTheNeutralElement) relScis.size match { case 0 => neutralElement case 1 => relScis.head - case _ => StringConstancyInformation(StringConstancyType.APPEND, StringTreeOr(relScis.map(_.tree))) + case _ if relScis.forall(_.isInstanceOf[StringConstancyInformationConst]) => + StringConstancyInformationConst(StringTreeOr(relScis.map(_.tree))) + case _ => + StringConstancyInformationFunction((pv: StringTreeNode) => StringTreeOr(relScis.map(_.treeFn(pv)))) } } - def lb: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicString) - def ub: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNeutralElement) - def dynamicInt: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicInt) - def dynamicFloat: StringConstancyInformation = StringConstancyInformation(tree = StringTreeDynamicFloat) + def lb: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicString) + def ub: StringConstancyInformation = StringConstancyInformationConst(StringTreeNeutralElement) + def dynamicInt: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicInt) + def dynamicFloat: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicFloat) - def neutralElement: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNeutralElement) - def nullElement: StringConstancyInformation = StringConstancyInformation(tree = StringTreeNull) + def neutralElement: StringConstancyInformation = StringConstancyInformationConst(StringTreeNeutralElement) + def nullElement: StringConstancyInformation = StringConstancyInformationConst(StringTreeNull) def getElementForParameterPC(paramPC: Int): StringConstancyInformation = { if (paramPC >= -1) { @@ -70,6 +73,6 @@ object StringConstancyInformation { } // Parameters start at PC -2 downwards val paramPosition = Math.abs(paramPC + 2) - StringConstancyInformation(tree = StringTreeParameter(paramPosition)) + StringConstancyInformationConst(StringTreeParameter(paramPosition)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 059fc2949c..f384e7c3bf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -15,7 +15,7 @@ import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -28,8 +28,8 @@ import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder import org.opalj.tac.fpcf.properties.TACAI /** @@ -39,6 +39,8 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringAnalysis extends FPCFAnalysis { + val pathFinder: PathFinder + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(data: SContext): ProperPropertyComputationResult = { @@ -87,7 +89,7 @@ trait StringAnalysis extends FPCFAnalysis { } } - if (SimplePathFinder.containsComplexControlFlow(tac)) { + if (pathFinder.containsComplexControlFlowForValue(uVar, tac)) { return Result(state.entity, StringConstancyProperty.lb) } @@ -162,8 +164,8 @@ trait StringAnalysis extends FPCFAnalysis { private def computeFinalResult(state: ComputationState): Result = { Result( state.entity, - StringConstancyProperty(StringConstancyInformation( - tree = PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify + StringConstancyProperty(StringConstancyInformationConst( + PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify )) ) } @@ -180,8 +182,8 @@ trait StringAnalysis extends FPCFAnalysis { private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { if (state.computedLeanPaths != null) { - StringConstancyProperty(StringConstancyInformation( - tree = PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify + StringConstancyProperty(StringConstancyInformationConst( + PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify )) } else { StringConstancyProperty.lb @@ -207,7 +209,7 @@ trait StringAnalysis extends FPCFAnalysis { if (initDefSites.isEmpty) { Seq.empty } else { - Seq(SimplePathFinder.findPath(tac).makeLeanPath(value)) + Seq(pathFinder.findPath(value, tac)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala index 5eeda30141..891dc2f06a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala @@ -74,7 +74,7 @@ object StringInterpreter { def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DUSiteState): Result = Result(FinalEP( InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty(sci.copy(tree = sci.tree.simplify)) + StringConstancyProperty(sci) )) } 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 f4a826f3e4..b913567190 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 @@ -14,8 +14,6 @@ import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType -import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString import org.opalj.fpcf.ProperPropertyComputationResult /** @@ -162,46 +160,6 @@ object InterpretationHandler { defSites.distinct.sorted.toList } - /** - * Determines the [[New]] expressions that belongs to a given `duvar`. - * - * @param value The [[V]] 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(value: V, stmts: Array[Stmt[V]]): List[New] = { - val news = ListBuffer[New]() - - // HINT: It might be that the search has to be extended to further cases - value.definedBy.filter(_ >= 0).foreach { ds => - stmts(ds) match { - // E.g., a call to `toString` or `append` - case Assignment(_, _, vfc: VirtualFunctionCall[V]) => - vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs => - stmts(innerDs) match { - case Assignment(_, _, expr: New) => - news.append(expr) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => - val exprReceiverVar = expr.receiver.asVar - // The "if" is to avoid endless recursion - if (value.definedBy != exprReceiverVar.definedBy) { - news.appendAll(findNewOfVar(exprReceiverVar, stmts)) - } - case _ => - } - } - case Assignment(_, _, newExpr: New) => - news.append(newExpr) - case _ => - } - } - - news.toList - } - - def getStringConstancyInformationForReplace: StringConstancyInformation = - StringConstancyInformation(StringConstancyType.REPLACE, StringTreeDynamicString) - def getEntityForDefSite(defSite: Int)(implicit state: DUSiteState): DUSiteEntity = getEntityForPC(pcOfDefSite(defSite)(state.tac.stmts)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala index b6dc3b554b..34fe4f4fc2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala @@ -7,7 +7,7 @@ package string_analysis package interpretation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult @@ -19,18 +19,15 @@ object SimpleValueConstExprInterpreter extends StringInterpreter { override type T = SimpleValueConst def interpret(expr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val treeOpt = expr match { - case ic: IntConst => Some(StringTreeConst(ic.value.toString)) - case fc: FloatConst => Some(StringTreeConst(fc.value.toString)) - case dc: DoubleConst => Some(StringTreeConst(dc.value.toString)) - case lc: LongConst => Some(StringTreeConst(lc.value.toString)) - case sc: StringConst => Some(StringTreeConst(sc.value)) - case _ => None + val sci = expr match { + case ic: IntConst => StringConstancyInformationConst(StringTreeConst(ic.value.toString)) + case fc: FloatConst => StringConstancyInformationConst(StringTreeConst(fc.value.toString)) + case dc: DoubleConst => StringConstancyInformationConst(StringTreeConst(dc.value.toString)) + case lc: LongConst => StringConstancyInformationConst(StringTreeConst(lc.value.toString)) + case sc: StringConst => StringConstancyInformationConst(StringTreeConst(sc.value)) + case _ => StringConstancyInformation.neutralElement } - val sci = treeOpt - .map(StringConstancyInformation(StringConstancyType.APPEND, _)) - .getOrElse(StringConstancyInformation.neutralElement) computeFinalResult(pc, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 8b75475c8c..2c32842057 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -9,11 +9,16 @@ package l0 import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder /** * @author Maximilian Rüsch */ -class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis +class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { + + override val pathFinder: PathFinder = SimplePathFinder +} object LazyL0StringAnalysis extends LazyStringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala index dd9ff9a813..420456761e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -11,6 +11,7 @@ import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -76,13 +77,11 @@ private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends St // For char values, we need to do a conversion (as the returned results are integers) val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { - scis.map { sci => - sci.tree match { - case const: StringTreeConst if const.isIntConst => - sci.copy(tree = StringTreeConst(const.string.toInt.toChar.toString)) - case _ => - sci - } + scis.map { + case StringConstancyInformationConst(const: StringTreeConst) if const.isIntConst => + StringConstancyInformationConst(StringTreeConst(const.string.toInt.toChar.toString)) + case sci => + sci } } else { scis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index c94286a20a..1812225d94 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -15,10 +15,12 @@ import org.opalj.br.FloatType 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.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationFunction import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat import org.opalj.br.fpcf.properties.string_definition.StringTreeConst +import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -87,10 +89,6 @@ case class L0VirtualFunctionCallInterpreter( } } - /** - * Processes 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: T, pc: Int)(implicit state: DUSiteState ): ProperPropertyComputationResult = { @@ -129,7 +127,7 @@ case class L0VirtualFunctionCallInterpreter( * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ private def interpretReplaceCall(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = - computeFinalResult(pc, InterpretationHandler.getStringConstancyInformationForReplace) + computeFinalResult(pc, StringConstancyInformationConst(StringTreeDynamicString)) } private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { @@ -150,20 +148,21 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter val ps: PropertyStore private[this] case class AppendCallState( - appendCall: T, - param: V, - defSitePC: Int, - state: DUSiteState, - var receiverDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]], - var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] + appendCall: T, + param: V, + defSitePC: Int, + state: DUSiteState, + var receiverDependeesOpt: Option[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]], + var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] ) { def updateDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { - if (receiverDependees.exists(_.e == newDependee.e)) { - receiverDependees = receiverDependees.updated( + if (receiverDependeesOpt.isDefined && receiverDependeesOpt.get.exists(_.e == newDependee.e)) { + val receiverDependees = receiverDependeesOpt.get + receiverDependeesOpt = Some(receiverDependees.updated( receiverDependees.indexWhere(_.e == newDependee.e), newDependee - ) + )) } else { valueDependees = valueDependees.updated( valueDependees.indexWhere(_.e == newDependee.e), @@ -173,19 +172,34 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter } def hasDependees: Boolean = - receiverDependees.exists(_.isRefinable) || valueDependees.exists(_.isRefinable) + (receiverDependeesOpt.isDefined && receiverDependeesOpt.get.exists(_.isRefinable)) || valueDependees.exists( + _.isRefinable + ) def dependees: Iterable[EOptionP[DUSiteEntity, StringConstancyProperty]] = - receiverDependees.filter(_.isRefinable) ++ valueDependees.filter(_.isRefinable) + receiverDependeesOpt.iterator.toSeq.flatMap(Seq.from).filter(_.isRefinable) ++ valueDependees.filter( + _.isRefinable + ) } def interpretAppendCall(appendCall: T, pc: Int)(implicit state: DUSiteState ): ProperPropertyComputationResult = { - // Get receiver results - val receiverResults = appendCall.receiver.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) + val receiverVar = appendCall.receiver.asVar + + val receiverResults = if (receiverVar.toPersistentForm(state.tac.stmts).equals(state.entity)) { + // Previous state of the receiver will be handled by path resolution + System.out.println("DAMN WE ACHIEVED IT!") + None + } else { + // Get receiver results + Some( + receiverVar.definedBy.toList.sorted.map { ds => + ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) + } + ) } + // Get parameter results // .head because we want to evaluate only the first argument of append val param = appendCall.params.head.asVar @@ -224,20 +238,20 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter continuation(appendState) ) } else { - val receiverSci = StringConstancyInformation.reduceMultiple(appendState.receiverDependees.map { - _.asFinal.p.sci - }) val valueSci = transformAppendValueResult( appendState.valueDependees.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]] ) - computeFinalResult( - appendState.defSitePC, - StringConstancyInformation( - StringConstancyType.APPEND, - StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree) - ) - )(appendState.state) + val resultSci = if (appendState.receiverDependeesOpt.isDefined) { + val receiverSci = StringConstancyInformation.reduceMultiple(appendState.receiverDependeesOpt.get.map { + _.asFinal.p.sci + }) + StringConstancyInformationConst(StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree)) + } else { + StringConstancyInformationFunction(pv => StringTreeConcat.fromNodes(pv, valueSci.tree)) + } + + computeFinalResult(appendState.defSitePC, resultSci)(appendState.state) } } @@ -254,8 +268,8 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter sciValues.exists(!_.isTheNeutralElement) ) { val charSciValues = sciValues map { - case sci @ StringConstancyInformation(_, const: StringTreeConst) if const.isIntConst => - sci.copy(tree = StringTreeConst(const.string.toInt.toChar.toString)) + case StringConstancyInformationConst(const: StringTreeConst) if const.isIntConst => + StringConstancyInformationConst(StringTreeConst(const.string.toInt.toChar.toString)) case sci => sci } @@ -323,8 +337,7 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre case intValue: TheIntegerValue => computeFinalResult( pc, - StringConstancyInformation( - StringConstancyType.REPLACE, + StringConstancyInformationConst( StringTreeConst( receiverSci.tree.asInstanceOf[StringTreeConst].string.substring(intValue.value) ) @@ -339,8 +352,7 @@ private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpre case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => computeFinalResult( pc, - StringConstancyInformation( - StringConstancyType.APPEND, + StringConstancyInformationConst( StringTreeConst( receiverSci.tree.asInstanceOf[StringTreeConst].string.substring( firstIntValue.value, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala index c7923e1ab1..d68c6eb3ce 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -8,7 +8,6 @@ package l0 package interpretation import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.fpcf.ProperPropertyComputationResult /** @@ -34,7 +33,7 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { val sci = instr.name match { // IMPROVE interpret argument for setLength - case "setLength" => StringConstancyInformation(StringConstancyType.RESET) + case "setLength" => StringConstancyInformation.neutralElement case _ => StringConstancyInformation.neutralElement } computeFinalResult(pc, sci) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 87c5ee2bcf..3ccd4d9c4d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -13,11 +13,16 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder /** * @author Maximilian Rüsch */ -class L1StringAnalysis(val project: SomeProject) extends StringAnalysis +class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { + + override val pathFinder: PathFinder = SimplePathFinder +} object L1StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala index 203ba0d1e9..f80242a0c5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala @@ -13,6 +13,7 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString import org.opalj.br.fpcf.properties.string_definition.StringTreeNull import org.opalj.br.fpcf.properties.string_definition.StringTreeOr @@ -114,9 +115,7 @@ case class L1FieldReadInterpreter( // No methods which write the field were found => Field could either be null or any value return computeFinalResult( pc, - StringConstancyInformation( - tree = StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) - ) + StringConstancyInformationConst(StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString)) ) } 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 379ade9e3f..8f13d2d53b 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 @@ -6,12 +6,6 @@ package analyses package string_analysis package preprocessing -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.value.ValueInformation - /** * A flat element, e.g., for representing a single statement. The statement is identified with `pc` by its [[org.opalj.br.PC]]. * @@ -19,8 +13,6 @@ import org.opalj.value.ValueInformation */ class PathElement private[PathElement] (val pc: Int) { def stmtIndex(implicit pcToIndex: Array[Int]): Int = valueOriginOfPC(pc, pcToIndex).get - - def copy = new PathElement(pc) } object PathElement { @@ -31,75 +23,4 @@ object PathElement { def fromPC(pc: Int) = new PathElement(pc) } -case class Path(elements: List[PathElement]) { - - /** - * 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]() - val stack = mutable.Stack[Int](obj.definedBy.toList: _*) - - while (stack.nonEmpty) { - val nextDefUseSite = stack.pop() - if (!defAndUses.contains(nextDefUseSite)) { - defAndUses.append(nextDefUseSite) - - stmts(nextDefUseSite) match { - case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] => - 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] => - stack.pushAll(a.targetVar.usedBy.toArray) - case _ => - } - } - } - - defAndUses.toList.sorted - } - - /** - * 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. - * - * @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])(implicit tac: TAC): Path = { - implicit val stmts: Array[Stmt[V]] = tac.stmts - implicit val pcToIndex: Array[Int] = tac.pcToIndex - - val newOfObj = InterpretationHandler.findNewOfVar(obj, stmts) - // Transform the list of relevant pcs into a map to have a constant access time - val defUseSites = getAllDefAndUseSites(obj, stmts) - val pcMap = defUseSites.filter { dus => - stmts(dus) match { - case Assignment(_, _, expr: VirtualFunctionCall[V]) => - val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) - newOfObj == news || news.exists(newOfObj.contains) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => - val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) - newOfObj == news || news.exists(newOfObj.contains) - case _ => true - } - }.map { s => (pcOfDefSite(s), ()) }.toMap - val leanPath = ListBuffer[PathElement]() - val endSite = obj.definedBy.toArray.max - - elements.foreach { - case fpe: PathElement if pcMap.contains(fpe.pc) && fpe.stmtIndex <= endSite => leanPath.append(fpe) - case _ => - } - - Path(leanPath.toList) - } -} +case class Path(elements: List[PathElement]) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala similarity index 58% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala index 8e674f16cd..32ff1178fe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/SimplePathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala @@ -6,12 +6,19 @@ package analyses package string_analysis package preprocessing -object SimplePathFinder { +trait PathFinder { + def containsComplexControlFlowForValue(value: V, tac: TAC): Boolean - /** - * Checks if the given TAC may contain any control structures - */ - def containsComplexControlFlow(tac: TAC): Boolean = { + def findPath(value: V, tac: TAC): Path +} + +/** + * Implements very simple path finding, by checking if the given TAC may contain any control structures and creating a + * path from the very first to the very last statement of the underlying CFG. + */ +object SimplePathFinder extends PathFinder { + + def containsComplexControlFlowForValue(value: V, tac: TAC): Boolean = { tac.stmts.exists { case _: If[V] => true case _: Switch[V] => true @@ -25,7 +32,7 @@ object SimplePathFinder { /** * Always returns a path from the first to the last statement pc in the CFG of the given TAC */ - def findPath(tac: TAC): Path = { + def findPath(value: V, tac: TAC): Path = { val cfg = tac.cfg Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(PathElement.fromPC).toList) } From c82b6d619678b6451e0b38a25093c6430227c6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 5 May 2024 19:59:47 +0200 Subject: [PATCH 405/583] Introduce path finder for structural analysis --- .../string_analysis/StringAnalysis.scala | 65 ++++----- .../InterpretationHandler.scala | 124 +----------------- .../string_analysis/l0/L0StringAnalysis.scala | 4 +- .../L0InterpretationHandler.scala | 8 +- .../L0NonVirtualMethodCallInterpreter.scala | 3 +- .../L0VirtualFunctionCallInterpreter.scala | 87 ++++++------ .../L1InterpretationHandler.scala | 2 +- .../string_analysis/preprocessing/Path.scala | 3 + .../preprocessing/PathFinder.scala | 87 +++++++++++- .../preprocessing/PathTransformer.scala | 60 --------- 10 files changed, 163 insertions(+), 280 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index f384e7c3bf..857208f4c2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -7,6 +7,7 @@ package string_analysis import org.opalj.br.FieldType import org.opalj.br.Method +import org.opalj.br.ObjectType import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys @@ -16,6 +17,8 @@ import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -29,7 +32,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.properties.TACAI /** @@ -89,14 +91,14 @@ trait StringAnalysis extends FPCFAnalysis { } } - if (pathFinder.containsComplexControlFlowForValue(uVar, tac)) { - return Result(state.entity, StringConstancyProperty.lb) - } - if (state.computedLeanPaths == null) { state.computedLeanPaths = computeLeanPaths(uVar) } + if (state.computedLeanPaths.isEmpty) { + return Result(state.entity, StringConstancyProperty.lb) + } + state.computedLeanPaths.flatMap(_.elements.map(_.pc)).distinct.foreach { pc => propertyStore( InterpretationHandler.getEntityForPC(pc, state.dm, tac, state.entity._1), @@ -161,14 +163,7 @@ trait StringAnalysis extends FPCFAnalysis { * not have been called)! * @return Returns the final result. */ - private def computeFinalResult(state: ComputationState): Result = { - Result( - state.entity, - StringConstancyProperty(StringConstancyInformationConst( - PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify - )) - ) - } + private def computeFinalResult(state: ComputationState): Result = Result(state.entity, computeNewUpperBound(state)) private def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { InterimResult( @@ -182,34 +177,27 @@ trait StringAnalysis extends FPCFAnalysis { private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { if (state.computedLeanPaths != null) { - StringConstancyProperty(StringConstancyInformationConst( - PathTransformer.pathsToStringTree(state.computedLeanPaths)(state, ps).simplify - )) + val reducedStringTree = if (state.computedLeanPaths.isEmpty) { + StringTreeNeutralElement + } else { + StringTreeOr(state.computedLeanPaths.map { pathFinder.transformPath(_, state.tac)(state, ps) }) + } + + StringConstancyProperty(StringConstancyInformationConst(reducedStringTree.simplify)) } else { StringConstancyProperty.lb } } private def computeLeanPaths(value: V)(implicit tac: TAC): Seq[Path] = { - val defSites = value.definedBy.toArray.sorted - - val call = tac.stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - computeLeanPathsForStringBuilder(value) - } else { - computeLeanPathsForStringConst(value)(tac.stmts) - } - } - - private def computeLeanPathsForStringConst(value: V)(implicit stmts: Array[Stmt[V]]): Seq[Path] = - value.definedBy.toList.sorted.map(ds => Path(List(PathElement(ds)))) - - private def computeLeanPathsForStringBuilder(value: V)(implicit tac: TAC): Seq[Path] = { - val initDefSites = InterpretationHandler.findDefSiteOfInit(value, tac.stmts) - if (initDefSites.isEmpty) { - Seq.empty + if (value.value.isReferenceValue && ( + value.value.asReferenceValue.asReferenceType.mostPreciseObjectType == ObjectType.StringBuilder + || value.value.asReferenceValue.asReferenceType.mostPreciseObjectType == ObjectType.StringBuffer + ) + ) { + pathFinder.findPath(value, tac).map(Seq(_)).getOrElse(Seq.empty) } else { - Seq(pathFinder.findPath(value, tac)) + value.definedBy.toList.sorted.map(ds => Path(List(PathElement(ds)(tac.stmts)))) } } } @@ -220,14 +208,7 @@ object StringAnalysis { * 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 = - v.value.isPrimitiveValue && isSupportedPrimitiveNumberType(v.value.asPrimitiveValue.primitiveType.toJava) - - /** - * 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 = + private def isSupportedPrimitiveNumberType(typeName: String): Boolean = typeName == "short" || typeName == "int" || typeName == "float" || typeName == "double" /** 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 b913567190..014d43d50d 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 @@ -6,13 +6,9 @@ package analyses package string_analysis package interpretation -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod -import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult @@ -29,7 +25,7 @@ abstract class InterpretationHandler { def analyze(entity: DUSiteEntity): ProperPropertyComputationResult = { val pc = entity.pc - implicit val defSiteState: DUSiteState = DUSiteState(pc, entity.dm, entity.tac, entity.entity) + implicit val duSiteState: DUSiteState = DUSiteState(pc, entity.dm, entity.tac, entity.entity) if (pc <= FormalParametersOriginOffset) { if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) @@ -38,128 +34,14 @@ abstract class InterpretationHandler { } } - processNewDefSitePC(pc) + processNewPC(pc) } - protected def processNewDefSitePC(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult + protected def processNewPC(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult } object InterpretationHandler { - /** - * Checks whether an expression contains a call to [[StringBuilder#toString]] or [[StringBuffer#toString]]. - */ - def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = - expr match { - case VirtualFunctionCall(_, clazz, _, "toString", _, _, _) => - clazz.mostPreciseObjectType == ObjectType.StringBuilder || - clazz.mostPreciseObjectType == ObjectType.StringBuffer - case _ => false - } - - /** - * Checks whether the given expression is a string constant / string literal. - */ - 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 { _ == ObjectType.String } - } else { - false - } - } - - /** - * Returns `true` if the given expressions is a primitive number type - */ - def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = - expr.asVar.value.isPrimitiveValue && - StringAnalysis.isSupportedPrimitiveNumberType( - expr.asVar.value.asPrimitiveValue.primitiveType.toJava - ) - - /** - * Checks whether an expression contains a call to [[StringBuilder#append]] or [[StringBuffer#append]]. - */ - def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { - expr match { - case VirtualFunctionCall(_, clazz, _, "append", _, _, _) => - clazz.mostPreciseObjectType == ObjectType.StringBuilder || - clazz.mostPreciseObjectType == ObjectType.StringBuffer - case _ => false - } - } - - /** - * Helper function for [[findDefSiteOfInit]]. - */ - 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.empty - } - - val defSites = ListBuffer[Int]() - val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.filter(_ >= 0).toList: _*) - val seenElements: mutable.Map[Int, Unit] = mutable.Map() - 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] => - 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.filter(!seenElements.contains(_))) - } else { - defSites.append(next) - } - case _: GetField[V] => - defSites.append(next) - case _ => // E.g., NullExpr - } - case _ => - } - seenElements(next) = () - } - - 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 value The [[V]] 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(value: V, stmts: Array[Stmt[V]]): List[Int] = { - val defSites = ListBuffer[Int]() - value.definedBy.foreach { ds => - 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) - }) - } - // If no init sites could be determined, use the definition sites of the UVar - if (defSites.isEmpty) { - defSites.appendAll(value.definedBy.toArray) - } - - defSites.distinct.sorted.toList - } - def getEntityForDefSite(defSite: Int)(implicit state: DUSiteState): DUSiteEntity = getEntityForPC(pcOfDefSite(defSite)(state.tac.stmts)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 2c32842057..210c851fd1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -10,14 +10,14 @@ import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.StructuralAnalysisPathFinder /** * @author Maximilian Rüsch */ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - override val pathFinder: PathFinder = SimplePathFinder + override val pathFinder: PathFinder = StructuralAnalysisPathFinder } object LazyL0StringAnalysis extends LazyStringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala index cf6e42f152..ba30fec4e3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala @@ -26,15 +26,15 @@ class L0InterpretationHandler()( ps: PropertyStore ) extends InterpretationHandler { - override protected def processNewDefSitePC(pc: Int)(implicit + override protected def processNewPC(pc: Int)(implicit state: DUSiteState ): ProperPropertyComputationResult = { - val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); - if (defSiteOpt.isEmpty) { + val duSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); + if (duSiteOpt.isEmpty) { throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") } - state.tac.stmts(defSiteOpt.get) match { + state.tac.stmts(duSiteOpt.get) match { case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index d3bcd8f01c..df798eb36f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -33,7 +33,8 @@ case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringIn private def interpretInit(init: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { init.params.size match { - case 0 => computeFinalResult(pc, StringConstancyInformation.neutralElement) + case 0 => + computeFinalResult(pc, StringConstancyInformation.neutralElement) case _ => // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val results = init.params.head.asVar.definedBy.toList.map { ds => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 1812225d94..e2c0fa77f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -21,7 +21,9 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString +import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EUBP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimEP @@ -117,10 +119,15 @@ case class L0VirtualFunctionCallInterpreter( } } - computeResult(ps( - InterpretationHandler.getEntityForDefSite(call.receiver.asVar.definedBy.head), - StringConstancyProperty.key - )) + val persistentReceiverVar = call.receiver.asVar.toPersistentForm(state.tac.stmts) + if (persistentReceiverVar.equals(state.entity)) { + computeFinalResult(pc, StringConstancyInformationFunction(sci => sci)) + } else { + computeResult(ps( + (persistentReceiverVar, state.dm.definedMethod), + StringConstancyProperty.key + )) + } } /** @@ -148,56 +155,50 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter val ps: PropertyStore private[this] case class AppendCallState( - appendCall: T, - param: V, - defSitePC: Int, - state: DUSiteState, - var receiverDependeesOpt: Option[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]], - var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] + appendCall: T, + param: V, + defSitePC: Int, + state: DUSiteState, + var receiverDependeeOpt: Option[EOptionP[SContext, StringConstancyProperty]], + var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] ) { - def updateDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { - if (receiverDependeesOpt.isDefined && receiverDependeesOpt.get.exists(_.e == newDependee.e)) { - val receiverDependees = receiverDependeesOpt.get - receiverDependeesOpt = Some(receiverDependees.updated( - receiverDependees.indexWhere(_.e == newDependee.e), - newDependee - )) - } else { - valueDependees = valueDependees.updated( - valueDependees.indexWhere(_.e == newDependee.e), - newDependee - ) + def updateReceiverDependee(newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { + if (receiverDependeeOpt.isEmpty) { + throw new IllegalStateException("Encountered update of receiver when no dependee was defined!") } + + receiverDependeeOpt = Some(newDependee) + } + + def updateValueDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { + valueDependees = valueDependees.updated(valueDependees.indexWhere(_.e == newDependee.e), newDependee) } def hasDependees: Boolean = - (receiverDependeesOpt.isDefined && receiverDependeesOpt.get.exists(_.isRefinable)) || valueDependees.exists( + (receiverDependeeOpt.isDefined && receiverDependeeOpt.get.isRefinable) || valueDependees.exists( _.isRefinable ) - def dependees: Iterable[EOptionP[DUSiteEntity, StringConstancyProperty]] = - receiverDependeesOpt.iterator.toSeq.flatMap(Seq.from).filter(_.isRefinable) ++ valueDependees.filter( - _.isRefinable - ) + def dependees: Iterable[EOptionP[Entity, StringConstancyProperty]] = { + if (receiverDependeeOpt.isDefined && receiverDependeeOpt.get.isRefinable) { + valueDependees.filter(_.isRefinable) :+ receiverDependeeOpt.get + } else { + valueDependees.filter(_.isRefinable) + } + } } def interpretAppendCall(appendCall: T, pc: Int)(implicit state: DUSiteState ): ProperPropertyComputationResult = { - val receiverVar = appendCall.receiver.asVar - - val receiverResults = if (receiverVar.toPersistentForm(state.tac.stmts).equals(state.entity)) { + val receiverVar = appendCall.receiver.asVar.toPersistentForm(state.tac.stmts) + val receiverResult = if (receiverVar.equals(state.entity)) { // Previous state of the receiver will be handled by path resolution - System.out.println("DAMN WE ACHIEVED IT!") None } else { // Get receiver results - Some( - receiverVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) - } - ) + Some(ps((receiverVar, state.dm.definedMethod), StringConstancyProperty.key)) } // Get parameter results @@ -212,15 +213,19 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter ps(InterpretationHandler.getEntityForDefSite(usedDS), StringConstancyProperty.key) } implicit val appendState: AppendCallState = - AppendCallState(appendCall, param, pc, state, receiverResults, valueResults) + AppendCallState(appendCall, param, pc, state, receiverResult, valueResults) tryComputeFinalAppendCallResult } private def continuation(appendState: AppendCallState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { + case EUBP(_: DUSiteEntity, _: StringConstancyProperty) => + appendState.updateValueDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) + tryComputeFinalAppendCallResult(appendState) + case UBP(_: StringConstancyProperty) => - appendState.updateDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) + appendState.updateReceiverDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) tryComputeFinalAppendCallResult(appendState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") @@ -242,10 +247,8 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter appendState.valueDependees.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]] ) - val resultSci = if (appendState.receiverDependeesOpt.isDefined) { - val receiverSci = StringConstancyInformation.reduceMultiple(appendState.receiverDependeesOpt.get.map { - _.asFinal.p.sci - }) + val resultSci = if (appendState.receiverDependeeOpt.isDefined) { + val receiverSci = appendState.receiverDependeeOpt.get.asFinal.p.sci StringConstancyInformationConst(StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree)) } else { StringConstancyInformationFunction(pv => StringTreeConcat.fromNodes(pv, valueSci.tree)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala index a974e169f3..f504b97c4d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala @@ -42,7 +42,7 @@ class L1InterpretationHandler( val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processNewDefSitePC(pc: Int)(implicit + override protected def processNewPC(pc: Int)(implicit state: DUSiteState ): ProperPropertyComputationResult = { val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); 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 8f13d2d53b..7358b1a2a8 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 @@ -12,6 +12,9 @@ package preprocessing * @author Maximilian Rüsch */ class PathElement private[PathElement] (val pc: Int) { + + override def toString: String = s"PathElement(pc=$pc)" + def stmtIndex(implicit pcToIndex: Array[Int]): Int = valueOriginOfPC(pc, pcToIndex).get } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala index 32ff1178fe..3ef8ec7eaa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala @@ -6,10 +6,35 @@ package analyses package string_analysis package preprocessing +import org.opalj.br.fpcf.properties.StringConstancyProperty +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.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string_definition.StringTreeNode +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler + trait PathFinder { - def containsComplexControlFlowForValue(value: V, tac: TAC): Boolean - def findPath(value: V, tac: TAC): Path + def findPath(value: V, tac: TAC): Option[Path] + + def transformPath(path: Path, tac: TAC)(implicit state: ComputationState, ps: PropertyStore): StringTreeNode + + protected def evaluatePathElement(pe: PathElement)(implicit + state: ComputationState, + ps: PropertyStore + ): Option[StringConstancyInformation] = { + val sci = ps( + InterpretationHandler.getEntityForPC(pe.pc, state.dm, state.tac, state.entity._1), + StringConstancyProperty.key + ) match { + case UBP(scp) => scp.sci + case _ => StringConstancyInformation.lb + } + Option.unless(sci.isTheNeutralElement)(sci) + } } /** @@ -18,8 +43,8 @@ trait PathFinder { */ object SimplePathFinder extends PathFinder { - def containsComplexControlFlowForValue(value: V, tac: TAC): Boolean = { - tac.stmts.exists { + override def findPath(value: V, tac: TAC): Option[Path] = { + val containsComplexControlFlow = tac.stmts.exists { case _: If[V] => true case _: Switch[V] => true case _: Throw[V] => true @@ -27,13 +52,61 @@ object SimplePathFinder extends PathFinder { case _: JSR => true case _ => false } || tac.cfg.catchNodes.nonEmpty + + if (containsComplexControlFlow) { + None + } else { + val cfg = tac.cfg + Some(Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(PathElement.fromPC).toList)) + } + } + + override def transformPath(path: Path, tac: TAC)(implicit + state: ComputationState, + ps: PropertyStore + ): StringTreeNode = { + path.elements.size match { + case 1 => + evaluatePathElement(path.elements.head).map(_.tree).getOrElse(StringTreeDynamicString) + case _ => + StringTreeConcat(path.elements.flatMap(evaluatePathElement).map(_.tree)) + } } +} + +object StructuralAnalysisPathFinder extends PathFinder { /** * Always returns a path from the first to the last statement pc in the CFG of the given TAC */ - def findPath(value: V, tac: TAC): Path = { - val cfg = tac.cfg - Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(PathElement.fromPC).toList) + override def findPath(value: V, tac: TAC): Option[Path] = { + val structural = new StructuralAnalysis(tac.cfg) + + if (structural.graph.isCyclic || value.definedBy.size > 1) { + return None; + } + + val defSite = value.definedBy.head + val allDefAndUseSites = tac.stmts(defSite).asAssignment.targetVar.usedBy.+(defSite).toList.sorted + + // val (_, _) = structural.analyze(structural.graph, Region(Block, Set(tac.cfg.startBlock.nodeId))) + + Some(Path(allDefAndUseSites.map(PathElement.apply(_)(tac.stmts)))) + } + + override def transformPath(path: Path, tac: TAC)(implicit + state: ComputationState, + ps: PropertyStore + ): StringTreeNode = { + path.elements.size match { + case 1 => + evaluatePathElement(path.elements.head).map(_.tree).getOrElse(StringTreeDynamicString) + case _ => + // IMPROVE handle entire control tree here + val evaluatedElements = path.elements.flatMap(evaluatePathElement) + evaluatedElements.foldLeft[StringTreeNode](StringTreeNeutralElement) { + (currentState, nextSci) => nextSci.treeFn(currentState) + } + } } } 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 deleted file mode 100644 index d8827c64fa..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string_analysis -package preprocessing - -import org.opalj.br.fpcf.properties.StringConstancyProperty -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.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string_definition.StringTreeNode -import org.opalj.br.fpcf.properties.string_definition.StringTreeOr -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler - -/** - * Transforms a [[Path]] into a string tree of [[StringTreeNode]]s. - * - * @author Maximilian Rüsch - */ -object PathTransformer { - - private def pathToTreeAcc(subpath: PathElement)(implicit - state: ComputationState, - ps: PropertyStore - ): Option[StringTreeNode] = { - val sci = ps( - InterpretationHandler.getEntityForPC(subpath.pc, state.dm, state.tac, state.entity._1), - StringConstancyProperty.key - ) match { - case UBP(scp) => scp.sci - case _ => StringConstancyInformation.lb - } - Option.unless(sci.isTheNeutralElement)(sci.tree) - } - - def pathsToStringTree(paths: Seq[Path])(implicit - state: ComputationState, - ps: PropertyStore - ): StringTreeNode = { - if (paths.isEmpty) { - StringTreeNeutralElement - } else { - val nodes = paths.map { path => - path.elements.size match { - case 1 => - pathToTreeAcc(path.elements.head).getOrElse(StringTreeDynamicString) - case _ => - StringTreeConcat(path.elements.flatMap(pathToTreeAcc(_))) - } - } - - StringTreeOr(nodes) - } - } -} From b62fd8ff34ecb5f00da7089270749ed47bd89e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 21:04:19 +0200 Subject: [PATCH 406/583] Introduce hyperedges to better visualize structural analysis --- .../preprocessing/StructuralAnalysis.scala | 74 +++++++++++++++++-- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala index a3f9742219..277d2bb6e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala @@ -10,9 +10,21 @@ import scala.collection.mutable import org.opalj.br.cfg.CFG +import scalax.collection.OneOrMore import scalax.collection.edges.DiEdge +import scalax.collection.generic.Edge +import scalax.collection.hyperedges.DiHyperEdge import scalax.collection.immutable.Graph -import scalax.collection.io.dot._ +import scalax.collection.io.dot.DotAttr +import scalax.collection.io.dot.DotAttrStmt +import scalax.collection.io.dot.DotEdgeStmt +import scalax.collection.io.dot.DotGraph +import scalax.collection.io.dot.DotNodeStmt +import scalax.collection.io.dot.DotRootGraph +import scalax.collection.io.dot.Elem +import scalax.collection.io.dot.Graph2DotExport +import scalax.collection.io.dot.Id +import scalax.collection.io.dot.NodeId trait RegionType extends Product @@ -54,7 +66,7 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { } val entry: Region = Region(Block, Set(cfg.startBlock.nodeId)) - def graphDot(graph: Graph[Region, DiEdge[Region]]): String = { + def graphDot[N <: Region, E <: Edge[N]](graph: Graph[N, E]): String = { val root = DotRootGraph( directed = true, id = Some(Id("MyDot")), @@ -62,27 +74,53 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { attrList = List(DotAttr(Id("attr_1"), Id(""""one"""")), DotAttr(Id("attr_2"), Id(""))) ) - def edgeTransformer(innerEdge: SGraph#EdgeT): Option[(DotGraph, DotEdgeStmt)] = { + def edgeTransformer(innerEdge: Graph[N, E]#EdgeT): Option[(DotGraph, DotEdgeStmt)] = { val edge = innerEdge.outer Some( ( root, - DotEdgeStmt(NodeId(edge.source.toString), NodeId(edge.target.toString)) + DotEdgeStmt(NodeId(edge.sources.head.toString), NodeId(edge.targets.head.toString)) ) ) } - def iNodeTransformer(innerNode: SGraph#NodeT): Option[(DotGraph, DotNodeStmt)] = { + def hEdgeTransformer(innerHEdge: Graph[N, E]#EdgeT): Iterable[(DotGraph, DotEdgeStmt)] = { + val color = DotAttr(Id("color"), Id("#%06x".format(scala.util.Random.nextInt(1 << 24)))) + + innerHEdge.outer.targets.toList map (target => + ( + root, + DotEdgeStmt( + NodeId(innerHEdge.outer.sources.head.toString), + NodeId(target.toString), + Seq(color) + ) + ) + ) + } + + def nodeTransformer(innerNode: Graph[N, E]#NodeT): Option[(DotGraph, DotNodeStmt)] = { val node = innerNode.outer + val attributes = if (node.nodeIds.size == 1) Seq.empty + else Seq( + DotAttr(Id("style"), Id("filled")), + DotAttr(Id("fillcolor"), Id("\"green\"")) + ) Some( ( root, - DotNodeStmt(NodeId(node.toString)) + DotNodeStmt(NodeId(node.toString), attributes) ) ) } - graph.toDot(root, edgeTransformer, iNodeTransformer = Some(iNodeTransformer)) + graph.toDot( + root, + edgeTransformer, + hEdgeTransformer = Some(hEdgeTransformer), + cNodeTransformer = Some(nodeTransformer), + iNodeTransformer = Some(nodeTransformer) + ) } def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { @@ -161,6 +199,28 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { (g, controlTree) } + + def combine(cfg: SGraph, controlTree: SGraph): Graph[Region, Edge[Region]] = { + var combinedGraph = cfg + .++[Region, DiEdge[Region]](controlTree.nodes.map(_.outer), Iterable.empty) + .asInstanceOf[Graph[Region, Edge[Region]]] + + for { + iNode <- controlTree.nodes + nodes = combinedGraph.nodes.filter((n: Graph[Region, Edge[Region]]#NodeT) => + n.outer.nodeIds.subsetOf(iNode.outer.nodeIds) + ).map(_.outer) + actualSubsetNodes = nodes.filter(n => n.nodeIds != iNode.outer.nodeIds) + remainingNodes = actualSubsetNodes.filter(n => + !actualSubsetNodes.exists(nn => n.nodeIds != nn.nodeIds && n.nodeIds.subsetOf(nn.nodeIds)) + ) + if remainingNodes.size > 1 + } { + combinedGraph = combinedGraph.incl(DiHyperEdge(OneOrMore(iNode.outer), OneOrMore.from(remainingNodes).get)) + } + + combinedGraph + } } object PostOrderTraversal { From 64ebe9dd2ccc4035401493c750283c62290b4d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 21:11:43 +0200 Subject: [PATCH 407/583] Stabilize post order traversal --- .../preprocessing/StructuralAnalysis.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala index 277d2bb6e8..a6d71759b8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala @@ -167,7 +167,9 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { (newGraph, newRegion) } - PostOrderTraversal.foreachInTraversalFrom[Region, SGraph](g, curEntry)(post.append) + PostOrderTraversal.foreachInTraversalFrom[Region, SGraph](g, curEntry)(post.append) { (x, y) => + x.nodeIds.head.compare(y.nodeIds.head) + } while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) @@ -229,19 +231,19 @@ object PostOrderTraversal { graph: G, toVisit: Seq[A], visited: Set[A] - )(nodeHandler: A => Unit): Unit = { + )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { if (toVisit.nonEmpty) { val next = toVisit.head - // TODO stabilize traversal - val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toSeq + val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toList.sorted foreachInTraversal(graph, nextSuccessors ++ toVisit.tail, visited + next)(nodeHandler) nodeHandler(next) } } - def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit): Unit = - foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) + def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( + implicit ordering: Ordering[A] + ): Unit = foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) } object AcyclicRegionType { From ee598a67f2eadd31b712a266b318faffb53d1f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 21:45:41 +0200 Subject: [PATCH 408/583] Break up basic blocks in structural analysis --- .../preprocessing/StructuralAnalysis.scala | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala index a6d71759b8..4f92a8a416 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala @@ -8,6 +8,7 @@ package preprocessing import scala.collection.mutable +import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG import scalax.collection.OneOrMore @@ -54,13 +55,30 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { type ControlTree = Graph[Region, DiEdge[Region]] val graph: SGraph = { - val edges = cfg.allNodes.flatMap(n => - n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) - ).toSet + val edges = cfg.allNodes.flatMap { + case bb: BasicBlock => + val firstNode = Region(Block, Set(bb.startPC)) + var currentEdges = Seq.empty[DiEdge[Region]] + if (bb.startPC != bb.endPC) { + Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrPC => + currentEdges :+= DiEdge( + currentEdges.lastOption.map(_.target).getOrElse(firstNode), + Region(Block, Set(instrPC)) + ) + } + } + + val lastNode = if (currentEdges.nonEmpty) currentEdges.last.target + else firstNode + currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Region(Block, Set(s.nodeId)))) + case n => + n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) + }.toSet var g = Graph.from(edges) - g = g.incl(DiEdge(Region(Block, Set(cfg.normalReturnNode.nodeId)), Region(Block, Set(-42)))) - g = g.incl(DiEdge(Region(Block, Set(cfg.abnormalReturnNode.nodeId)), Region(Block, Set(-42)))) + val allReturnNode = Region(Block, Set(-42)) + g = g.incl(DiEdge(Region(Block, Set(cfg.normalReturnNode.nodeId)), allReturnNode)) + g = g.incl(DiEdge(Region(Block, Set(cfg.abnormalReturnNode.nodeId)), allReturnNode)) g } From 81f3b3abb52a7e00465735ce730beedb7213d902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 21:51:52 +0200 Subject: [PATCH 409/583] Remove pathfinder hierarchy in favor of structural analysis based pathfinder --- .../string_analysis/StringAnalysis.scala | 6 +- .../string_analysis/l0/L0StringAnalysis.scala | 7 +-- .../string_analysis/l1/L1StringAnalysis.scala | 7 +-- .../preprocessing/PathFinder.scala | 56 ++----------------- 4 files changed, 8 insertions(+), 68 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala index 857208f4c2..8874f6e596 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala @@ -41,8 +41,6 @@ import org.opalj.tac.fpcf.properties.TACAI */ trait StringAnalysis extends FPCFAnalysis { - val pathFinder: PathFinder - val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(data: SContext): ProperPropertyComputationResult = { @@ -180,7 +178,7 @@ trait StringAnalysis extends FPCFAnalysis { val reducedStringTree = if (state.computedLeanPaths.isEmpty) { StringTreeNeutralElement } else { - StringTreeOr(state.computedLeanPaths.map { pathFinder.transformPath(_, state.tac)(state, ps) }) + StringTreeOr(state.computedLeanPaths.map { PathFinder.transformPath(_, state.tac)(state, ps) }) } StringConstancyProperty(StringConstancyInformationConst(reducedStringTree.simplify)) @@ -195,7 +193,7 @@ trait StringAnalysis extends FPCFAnalysis { || value.value.asReferenceValue.asReferenceType.mostPreciseObjectType == ObjectType.StringBuffer ) ) { - pathFinder.findPath(value, tac).map(Seq(_)).getOrElse(Seq.empty) + PathFinder.findPath(value, tac).map(Seq(_)).getOrElse(Seq.empty) } else { value.definedBy.toList.sorted.map(ds => Path(List(PathElement(ds)(tac.stmts)))) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala index 210c851fd1..8b75475c8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala @@ -9,16 +9,11 @@ package l0 import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.StructuralAnalysisPathFinder /** * @author Maximilian Rüsch */ -class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis { - - override val pathFinder: PathFinder = StructuralAnalysisPathFinder -} +class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis object LazyL0StringAnalysis extends LazyStringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala index 3ccd4d9c4d..87c5ee2bcf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala @@ -13,16 +13,11 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SimplePathFinder /** * @author Maximilian Rüsch */ -class L1StringAnalysis(val project: SomeProject) extends StringAnalysis { - - override val pathFinder: PathFinder = SimplePathFinder -} +class L1StringAnalysis(val project: SomeProject) extends StringAnalysis object L1StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala index 3ef8ec7eaa..97126a5aff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala @@ -8,7 +8,6 @@ package preprocessing import org.opalj.br.fpcf.properties.StringConstancyProperty 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.StringTreeDynamicString import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string_definition.StringTreeNode @@ -16,13 +15,9 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -trait PathFinder { +object PathFinder { - def findPath(value: V, tac: TAC): Option[Path] - - def transformPath(path: Path, tac: TAC)(implicit state: ComputationState, ps: PropertyStore): StringTreeNode - - protected def evaluatePathElement(pe: PathElement)(implicit + private def evaluatePathElement(pe: PathElement)(implicit state: ComputationState, ps: PropertyStore ): Option[StringConstancyInformation] = { @@ -35,51 +30,8 @@ trait PathFinder { } Option.unless(sci.isTheNeutralElement)(sci) } -} - -/** - * Implements very simple path finding, by checking if the given TAC may contain any control structures and creating a - * path from the very first to the very last statement of the underlying CFG. - */ -object SimplePathFinder extends PathFinder { - - override def findPath(value: V, tac: TAC): Option[Path] = { - val containsComplexControlFlow = tac.stmts.exists { - case _: If[V] => true - case _: Switch[V] => true - case _: Throw[V] => true - case _: Goto => true - case _: JSR => true - case _ => false - } || tac.cfg.catchNodes.nonEmpty - - if (containsComplexControlFlow) { - None - } else { - val cfg = tac.cfg - Some(Path(cfg.startBlock.startPC.until(cfg.code.instructions.last.pc).map(PathElement.fromPC).toList)) - } - } - - override def transformPath(path: Path, tac: TAC)(implicit - state: ComputationState, - ps: PropertyStore - ): StringTreeNode = { - path.elements.size match { - case 1 => - evaluatePathElement(path.elements.head).map(_.tree).getOrElse(StringTreeDynamicString) - case _ => - StringTreeConcat(path.elements.flatMap(evaluatePathElement).map(_.tree)) - } - } -} - -object StructuralAnalysisPathFinder extends PathFinder { - /** - * Always returns a path from the first to the last statement pc in the CFG of the given TAC - */ - override def findPath(value: V, tac: TAC): Option[Path] = { + def findPath(value: V, tac: TAC): Option[Path] = { val structural = new StructuralAnalysis(tac.cfg) if (structural.graph.isCyclic || value.definedBy.size > 1) { @@ -94,7 +46,7 @@ object StructuralAnalysisPathFinder extends PathFinder { Some(Path(allDefAndUseSites.map(PathElement.apply(_)(tac.stmts)))) } - override def transformPath(path: Path, tac: TAC)(implicit + def transformPath(path: Path, tac: TAC)(implicit state: ComputationState, ps: PropertyStore ): StringTreeNode = { From 97b0d2f79adce627aed0a0bf194b96d5b66fa169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 22:04:50 +0200 Subject: [PATCH 410/583] Simplify string analysis and definition package names --- .../info/StringAnalysisReflectiveCalls.scala | 12 ++++----- .../string_analysis/StringConstancyLevel.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 12 ++++----- .../StringAnalysisMatcher.scala | 2 +- .../StringConstancyInformation.scala | 2 +- .../StringConstancyLevel.scala | 2 +- .../StringConstancyProperty.scala | 7 ++--- .../StringConstancyType.scala | 2 +- .../StringTreeNode.scala | 2 +- .../StringConstancyLevelTests.scala | 8 +++--- .../ComputationState.scala | 4 +-- .../EPSDepender.scala | 4 +-- .../StringAnalysis.scala | 18 ++++++------- .../StringInterpreter.scala | 8 +++--- .../BinaryExprInterpreter.scala | 4 +-- .../InterpretationHandler.scala | 4 +-- .../SimpleValueConstExprInterpreter.scala | 8 +++--- .../l0/L0StringAnalysis.scala | 4 +-- .../L0ArrayAccessInterpreter.scala | 8 +++--- .../L0FunctionCallInterpreter.scala | 8 +++--- .../L0InterpretationHandler.scala | 10 +++---- .../L0NewArrayInterpreter.scala | 8 +++--- .../L0NonVirtualFunctionCallInterpreter.scala | 4 +-- .../L0NonVirtualMethodCallInterpreter.scala | 8 +++--- .../L0StaticFunctionCallInterpreter.scala | 16 ++++++------ .../L0VirtualFunctionCallInterpreter.scala | 26 +++++++++---------- .../L0VirtualMethodCallInterpreter.scala | 4 +-- .../l1/L1StringAnalysis.scala | 4 +-- .../L1FieldReadInterpreter.scala | 20 +++++++------- .../L1InterpretationHandler.scala | 22 ++++++++-------- .../L1VirtualFunctionCallInterpreter.scala | 12 ++++----- .../preprocessing/Path.scala | 2 +- .../preprocessing/PathFinder.scala | 14 +++++----- .../preprocessing/StructuralAnalysis.scala | 2 +- .../string_analysis.scala | 2 +- 35 files changed, 136 insertions(+), 139 deletions(-) rename OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/{string_definition => string}/StringConstancyInformation.scala (99%) rename OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/{string_definition => string}/StringConstancyLevel.scala (99%) rename OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/{ => string}/StringConstancyProperty.scala (92%) rename OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/{string_definition => string}/StringConstancyType.scala (97%) rename OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/{string_definition => string}/StringTreeNode.scala (99%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/ComputationState.scala (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/EPSDepender.scala (83%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/StringAnalysis.scala (94%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/StringInterpreter.scala (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/interpretation/BinaryExprInterpreter.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/interpretation/InterpretationHandler.scala (95%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/interpretation/SimpleValueConstExprInterpreter.scala (81%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/L0StringAnalysis.scala (82%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0ArrayAccessInterpreter.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0FunctionCallInterpreter.scala (96%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0InterpretationHandler.scala (88%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0NewArrayInterpreter.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0StaticFunctionCallInterpreter.scala (86%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0VirtualFunctionCallInterpreter.scala (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l0/interpretation/L0VirtualMethodCallInterpreter.scala (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l1/L1StringAnalysis.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l1/interpretation/L1FieldReadInterpreter.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l1/interpretation/L1InterpretationHandler.scala (80%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/l1/interpretation/L1VirtualFunctionCallInterpreter.scala (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/preprocessing/Path.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/preprocessing/PathFinder.scala (80%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/preprocessing/StructuralAnalysis.scala (99%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/{string_analysis => string}/string_analysis.scala (96%) 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 b1827ba354..382511781e 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 @@ -16,9 +16,9 @@ import org.opalj.br.analyses.Project import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.br.fpcf.FPCFAnalysesManagerKey -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.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.instructions.Instruction import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.instructions.INVOKEVIRTUAL @@ -40,9 +40,9 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey -import org.opalj.tac.fpcf.analyses.string_analysis.SContext -import org.opalj.tac.fpcf.analyses.string_analysis.l0.LazyL0StringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.SContext +import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java index f4b7dda0fd..6409ff4194 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java @@ -3,7 +3,7 @@ /** * Java annotations do not work with Scala enums, such as - * {@link org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel}. Thus, this enum. + * {@link org.opalj.br.fpcf.properties.string.StringConstancyLevel}. Thus, this enum. * * @author Patrick Mell */ 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 b542e7591b..10ec19474f 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 @@ -12,7 +12,7 @@ import org.opalj.br.Method import org.opalj.br.ObjectType import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project -import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode @@ -20,9 +20,9 @@ import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.SEntity -import org.opalj.tac.fpcf.analyses.string_analysis.l0.LazyL0StringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.SEntity +import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -136,7 +136,7 @@ object StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.l0.L0StringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l0.L0StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch @@ -175,7 +175,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index 13791cdc55..2ef4b81e5d 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -7,7 +7,7 @@ package string_analysis import org.opalj.br.AnnotationLike import org.opalj.br.ObjectType import org.opalj.br.analyses.Project -import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringConstancyProperty /** * @author Maximilian Rüsch 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/StringConstancyInformation.scala similarity index 99% rename from OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala index 007d50b273..7b3da8b616 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/StringConstancyInformation.scala @@ -3,7 +3,7 @@ package org.opalj package br package fpcf package properties -package string_definition +package string /** * @author Maximilian Rüsch diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala similarity index 99% rename from OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index 3683de61e5..dc96484109 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -3,7 +3,7 @@ package org.opalj package br package fpcf package properties -package string_definition +package string /** * Values in this enumeration represent the granularity of used strings. 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/string/StringConstancyProperty.scala similarity index 92% rename from OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index 86be4aac12..f8dfd80782 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/string/StringConstancyProperty.scala @@ -3,8 +3,8 @@ package org.opalj package br package fpcf package properties +package string -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason import org.opalj.fpcf.Property @@ -53,10 +53,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta final val key: PropertyKey[StringConstancyProperty] = { PropertyKey.create( PropertyKeyName, - (_: PropertyStore, _: FallbackReason, _: Entity) => { - // TODO: Using simple heuristics, return a better value for some easy cases - lb - } + (_: PropertyStore, _: FallbackReason, _: Entity) => lb ) } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala similarity index 97% rename from OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala index 9f508c1933..25bfd0726b 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala @@ -3,7 +3,7 @@ package org.opalj package br package fpcf package properties -package string_definition +package string /** * Values in this enumeration represent how a string / string container, such as [[StringBuilder]], diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala similarity index 99% rename from OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index f21a7176db..a5b21f2aff 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -3,7 +3,7 @@ package org.opalj package br package fpcf package properties -package string_definition +package string import scala.collection.immutable.Seq import scala.collection.mutable 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 1b4db4bd14..68d2e1f796 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 @@ -5,10 +5,10 @@ package string_definition import org.scalatest.funsuite.AnyFunSuite -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 +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.CONSTANT +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.PARTIALLY_CONSTANT /** * Tests for [[StringConstancyLevel]] methods. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 01294d69ad..f28b734cc0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -4,13 +4,13 @@ package opalj package tac package fpcf package analyses -package string_analysis +package string import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string.preprocessing.Path import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala similarity index 83% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala index 15241f00a2..9af0d6d24e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/EPSDepender.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala @@ -3,14 +3,14 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string import org.opalj.fpcf.SomeEOptionP /** * @author Maximilian Rüsch */ -private[string_analysis] case class EPSDepender[T <: ASTNode[V]]( +private[string] case class EPSDepender[T <: ASTNode[V]]( instr: T, pc: Int, state: DUSiteState, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala similarity index 94% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 8874f6e596..e87001b881 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string import org.opalj.br.FieldType import org.opalj.br.Method @@ -15,10 +15,10 @@ 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.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string_definition.StringTreeOr +import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP @@ -28,10 +28,10 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathElement -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathFinder +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string.preprocessing.PathElement +import org.opalj.tac.fpcf.analyses.string.preprocessing.PathFinder import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 891dc2f06a..74d7ec6fb3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -3,10 +3,10 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP @@ -16,7 +16,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler /** * @author Maximilian Rüsch 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/interpretation/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/interpretation/BinaryExprInterpreter.scala index 95f53357d7..28182ce4a7 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/interpretation/BinaryExprInterpreter.scala @@ -3,12 +3,12 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package interpretation 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.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult /** 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/interpretation/InterpretationHandler.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 014d43d50d..b66c341ac2 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/interpretation/InterpretationHandler.scala @@ -3,13 +3,13 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package interpretation import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala similarity index 81% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala index 34fe4f4fc2..329da42a8a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala @@ -3,12 +3,12 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package interpretation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string_definition.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala similarity index 82% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala index 8b75475c8c..84d36370c9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala @@ -3,12 +3,12 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0InterpretationHandler /** * @author Maximilian Rüsch diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala index 1d1c965b77..8fd68596f7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0ArrayAccessInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala @@ -3,19 +3,19 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler /** * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions without a call graph. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index 64cc64b32e..9d7a56fa0e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -3,13 +3,13 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation 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.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP import org.opalj.fpcf.InterimResult @@ -17,7 +17,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala similarity index 88% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index ba30fec4e3..31ab797594 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -3,17 +3,17 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.SimpleValueConstExprInterpreter +import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter /** * @inheritdoc diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala index 6184a0c676..ac805ef9ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NewArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala @@ -3,19 +3,19 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler /** * @author Maximilian Rüsch diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 0b3eebf5e2..4e36e4b3bb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -3,12 +3,12 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala similarity index 90% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index df798eb36f..9d8ee837d8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -3,19 +3,19 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.FinalEP 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.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler /** * @author Maximilian Rüsch diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala similarity index 86% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 420456761e..496eb08c74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -3,22 +3,22 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string_definition.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI /** @@ -42,7 +42,7 @@ case class L0StaticFunctionCallInterpreter()( } } -private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter +private[string] trait L0ArbitraryStaticFunctionCallInterpreter extends StringInterpreter with L0FunctionCallInterpreter { @@ -66,7 +66,7 @@ private[string_analysis] trait L0ArbitraryStaticFunctionCallInterpreter } } -private[string_analysis] trait L0StringValueOfFunctionCallInterpreter extends StringInterpreter { +private[string] trait L0StringValueOfFunctionCallInterpreter extends StringInterpreter { override type T <: StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index e2c0fa77f4..f558839bfd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation @@ -13,14 +13,14 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.DoubleType import org.opalj.br.FloatType 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.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationFunction -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat -import org.opalj.br.fpcf.properties.string_definition.StringTreeConst -import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringConstancyInformationFunction +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeConcat +import org.opalj.br.fpcf.properties.string.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP @@ -35,7 +35,7 @@ import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.SomeFinalEP import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.value.TheIntegerValue /** @@ -137,7 +137,7 @@ case class L0VirtualFunctionCallInterpreter( computeFinalResult(pc, StringConstancyInformationConst(StringTreeDynamicString)) } -private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { +private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { protected def interpretArbitraryCall(call: T, pc: Int)(implicit state: DUSiteState @@ -148,7 +148,7 @@ private[string_analysis] trait L0ArbitraryVirtualFunctionCallInterpreter extends /** * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. */ -private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter { +private[string] trait L0AppendCallInterpreter extends StringInterpreter { override type T = VirtualFunctionCall[V] @@ -295,7 +295,7 @@ private[string_analysis] trait L0AppendCallInterpreter extends StringInterpreter /** * Interprets calls to [[String#substring]]. */ -private[string_analysis] trait L0SubstringCallInterpreter extends StringInterpreter { +private[string] trait L0SubstringCallInterpreter extends StringInterpreter { override type T = VirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala index d68c6eb3ce..49c34c624b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -3,11 +3,11 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l0 package interpretation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 87c5ee2bcf..5b1265dae7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l1 import org.opalj.br.analyses.ProjectInformationKeys @@ -12,7 +12,7 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.l1.interpretation.L1InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHandler /** * @author Maximilian Rüsch diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index f80242a0c5..a69d874872 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l1 package interpretation @@ -11,12 +11,12 @@ import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string_definition.StringTreeNull -import org.opalj.br.fpcf.properties.string_definition.StringTreeOr +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNull +import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.EOptionP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -27,8 +27,8 @@ import org.opalj.fpcf.UBP import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l1.L1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis /** * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields @@ -96,7 +96,7 @@ case class L1FieldReadInterpreter( * [[L1StringAnalysis]] 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 - * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]]. + * [[org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC]]. */ override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { // TODO: The approximation of fields might be outsourced into a dedicated analysis. Then, one could add a diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala similarity index 80% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index f504b97c4d..7b0aa41ca4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l1 package interpretation @@ -15,18 +15,18 @@ import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.BinaryExprInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.SimpleValueConstExprInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0ArrayAccessInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NewArrayInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualFunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0NonVirtualMethodCallInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0StaticFunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0ArrayAccessInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NewArrayInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0StaticFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualMethodCallInterpreter /** * @inheritdoc diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala similarity index 90% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index f9d58f520b..baf22d3e17 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package l1 package interpretation @@ -13,9 +13,9 @@ import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context -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.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult @@ -23,9 +23,9 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0FunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.l0.interpretation.L0VirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI /** 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/preprocessing/Path.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/Path.scala index 7358b1a2a8..72f67b9f5c 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/preprocessing/Path.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package preprocessing /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala similarity index 80% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala index 97126a5aff..48f5a0d82f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala @@ -3,17 +3,17 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package preprocessing -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string_definition.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string_definition.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler object PathFinder { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala index 4f92a8a416..155edf1f9d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala @@ -3,7 +3,7 @@ package org.opalj package tac package fpcf package analyses -package string_analysis +package string package preprocessing import scala.collection.mutable 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/string_analysis.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala index 74080a257a..0e8ae42429 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/string_analysis.scala @@ -10,7 +10,7 @@ import org.opalj.br.Method /** * @author Maximilian Rüsch */ -package object string_analysis { +package object string { type TAC = TACode[TACMethodParameter, V] From ca9787d8f7cf967d04a8725450e9d0e52e298894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 6 May 2024 23:22:27 +0200 Subject: [PATCH 411/583] Fix detection for cyclic graphs in structural analysis --- .../analyses/string/preprocessing/StructuralAnalysis.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala index 155edf1f9d..5696fc3d41 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala @@ -142,7 +142,9 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { } def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { - assert(graph.isAcyclic, "The passed graph must not be cyclic!") + if (graph.isCyclic) { + throw new IllegalArgumentException("The passed graph must not be cyclic!") + } var g = graph var curEntry = entry From a77c94cd118adfc7bd6bddd4974c66634d9e68ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 16 May 2024 15:12:46 +0200 Subject: [PATCH 412/583] Fix color coding for DOT export --- .../fpcf/analyses/string/preprocessing/StructuralAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala index 5696fc3d41..6efecd3857 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala @@ -103,7 +103,7 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { } def hEdgeTransformer(innerHEdge: Graph[N, E]#EdgeT): Iterable[(DotGraph, DotEdgeStmt)] = { - val color = DotAttr(Id("color"), Id("#%06x".format(scala.util.Random.nextInt(1 << 24)))) + val color = DotAttr(Id("color"), Id(s""""#%06x"""".format(scala.util.Random.nextInt(1 << 24)))) innerHEdge.outer.targets.toList map (target => ( From 185e606e349894bcc57212d4b2aafdc7ff81ef02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 16 May 2024 15:13:31 +0200 Subject: [PATCH 413/583] Fix detection of cycles in block types --- .../analyses/string/preprocessing/StructuralAnalysis.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala index 6efecd3857..6fbd212a53 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala @@ -327,7 +327,11 @@ object AcyclicRegionType { } val rType = if (nSet.size > 1) { - Some(Block) + // This condition was not contained in the book this algorithm is based on, why was it missing? + if (graph.filter(nodeP = node => nSet.contains(node.outer)).isAcyclic) + Some(Block) + else + None } else if (newDirectSuccessors.size == 2) { val m = newDirectSuccessors.head val k = newDirectSuccessors.tail.head From 2f5b4d5f7291c0cfec8b0a7535e79b18d1e0f83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 16 May 2024 21:15:21 +0200 Subject: [PATCH 414/583] Implement location of cycles structural analysis --- .../preprocessing/StructuralAnalysis.scala | 131 +++++++++++++++--- 1 file changed, 115 insertions(+), 16 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala index 6fbd212a53..efa476947c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala @@ -10,6 +10,7 @@ import scala.collection.mutable import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG +import org.opalj.graphs.DominatorTree import scalax.collection.OneOrMore import scalax.collection.edges.DiEdge @@ -74,13 +75,27 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { case n => n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) }.toSet - var g = Graph.from(edges) + val g = Graph.from(edges) - val allReturnNode = Region(Block, Set(-42)) - g = g.incl(DiEdge(Region(Block, Set(cfg.normalReturnNode.nodeId)), allReturnNode)) - g = g.incl(DiEdge(Region(Block, Set(cfg.abnormalReturnNode.nodeId)), allReturnNode)) + val normalReturnNode = Region(Block, Set(cfg.normalReturnNode.nodeId)) + val abnormalReturnNode = Region(Block, Set(cfg.abnormalReturnNode.nodeId)) + val hasNormalReturn = cfg.normalReturnNode.predecessors.nonEmpty + val hasAbnormalReturn = cfg.abnormalReturnNode.predecessors.nonEmpty - g + (hasNormalReturn, hasAbnormalReturn) match { + case (true, true) => + val allReturnNode = Region(Block, Set(-42)) + g.incl(DiEdge(normalReturnNode, allReturnNode)).incl(DiEdge(abnormalReturnNode, allReturnNode)) + + case (true, false) => + g.excl(abnormalReturnNode) + + case (false, true) => + g.excl(normalReturnNode) + + case _ => + throw new IllegalStateException("Cannot transform a CFG with neither normal nor abnormal return edges!") + } } val entry: Region = Region(Block, Set(cfg.startBlock.nodeId)) @@ -142,10 +157,6 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { } def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { - if (graph.isCyclic) { - throw new IllegalArgumentException("The passed graph must not be cyclic!") - } - var g = graph var curEntry = entry var controlTree = Graph.empty[Region, DiEdge[Region]] @@ -158,7 +169,6 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { def replace(g: SGraph, subRegions: Set[Region], regionType: RegionType): (SGraph, Region) = { val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) - var newGraph: SGraph = g // Compact @@ -176,9 +186,9 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { val source: Region = e.outer.source val target: Region = e.outer.target - if (!subRegions.contains(source) && subRegions.contains(target) && source != newRegion) { + if (!subRegions.contains(source) && subRegions.contains(target)) { newGraph += DiEdge(source, newRegion) - } else if (subRegions.contains(source) && !subRegions.contains(target) && target != newRegion) { + } else if (subRegions.contains(source) && !subRegions.contains(target)) { newGraph += DiEdge(newRegion, target) } } @@ -211,8 +221,46 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { curEntry = newRegion } } else { - // Detect cyclic region - postCtr += 1 + val indexedNodes = g.nodes.outerIterable.toList + val domTree = DominatorTree( + indexedNodes.indexOf(curEntry), + g.get(curEntry).diPredecessors.nonEmpty, + index => { f => + g.get(indexedNodes(index)).diSuccessors.foreach(ds => f(indexedNodes.indexOf(ds))) + }, + index => { f => + g.get(indexedNodes(index)).diPredecessors.foreach(ds => f(indexedNodes.indexOf(ds))) + }, + indexedNodes.size - 1 + ) + + var reachUnder = Set(n) + for { + m <- g.nodes.outerIterator + if !controlTree.contains(m) || controlTree.get(m).diPredecessors.isEmpty + if StructuralAnalysis.pathBack(g, indexedNodes, domTree)(m, n) + } { + reachUnder = reachUnder.incl(m) + } + + val cyclicRegionOpt = CyclicRegionType.locate(g, n, reachUnder) + if (cyclicRegionOpt.isDefined) { + val (crType, nodes) = cyclicRegionOpt.get + + val (newGraph, newRegion) = replace(g, nodes, crType) + g = newGraph + for { + node <- nodes + } { + controlTree = controlTree.incl(DiEdge(newRegion, node)) + } + + if (nodes.contains(curEntry)) { + curEntry = newRegion + } + } else { + postCtr += 1 + } } } @@ -245,6 +293,27 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { } } +object StructuralAnalysis { + + private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexedNodes: Seq[A], domTree: DominatorTree)( + m: A, + n: A + ): Boolean = { + if (m == n) { + false + } else { + val graphWithoutN = graph.excl(n) + graphWithoutN.nodes.outerIterable.exists { k => + graphWithoutN.get(m).pathTo( + graphWithoutN.get(k) + ).isDefined && + graph.edges.toOuter.contains(DiEdge(k, n)) && + (k == n || domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k))) + } + } + } +} + object PostOrderTraversal { private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( @@ -327,8 +396,9 @@ object AcyclicRegionType { } val rType = if (nSet.size > 1) { - // This condition was not contained in the book this algorithm is based on, why was it missing? - if (graph.filter(nodeP = node => nSet.contains(node.outer)).isAcyclic) + // Condition is added to ensure chosen bb does not contain any self loops or other cyclic stuff + // IMPROVE weaken to allow back edges from the "last" nSet member to the first to enable reductions to self loops + if (graph.filter(nSet.contains(_)).isAcyclic) Some(Block) else None @@ -369,3 +439,32 @@ object AcyclicRegionType { (newStartingNode, rType.map((_, nSet))) } } + +object CyclicRegionType { + + def locate[A, G <: Graph[A, DiEdge[A]]]( + graph: G, + startingNode: A, + reachUnder: Set[A] + ): Option[(CyclicRegionType, Set[A])] = { + if (reachUnder.size == 1) { + return if (graph.find(DiEdge(startingNode, startingNode)).isDefined) Some((SelfLoop, reachUnder)) + else None + } + + if (reachUnder.exists(m => graph.get(startingNode).pathTo(graph.get(m)).isEmpty)) { + throw new IllegalStateException("This implementation of structural analysis cannot handle improper regions!") + } + + val m = reachUnder.excl(startingNode).head + if (graph.get(startingNode).diPredecessors.size == 2 + && graph.get(startingNode).diSuccessors.size == 2 + && graph.get(m).diPredecessors.size == 1 + && graph.get(m).diSuccessors.size == 1 + ) { + Some((WhileLoop, reachUnder)) + } else { + Some((NaturalLoop, reachUnder)) + } + } +} From 286c8f095c84255258bdcf49dbe266801e2d3ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 11:23:45 +0200 Subject: [PATCH 415/583] Refactor structural analysis structure --- .../analyses/string/flowanalysis/Region.scala | 27 ++ .../StructuralAnalysis.scala | 233 +++--------------- .../string/flowanalysis/package.scala | 164 ++++++++++++ .../string/preprocessing/PathFinder.scala | 7 +- 4 files changed, 227 insertions(+), 204 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{preprocessing => flowanalysis}/StructuralAnalysis.scala (60%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala new file mode 100644 index 0000000000..79e928c478 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package flowanalysis + +trait RegionType extends Product + +trait AcyclicRegionType extends RegionType +trait CyclicRegionType extends RegionType + +case object Block extends AcyclicRegionType +case object IfThen extends AcyclicRegionType +case object IfThenElse extends AcyclicRegionType +case object Case extends AcyclicRegionType +case object Proper extends AcyclicRegionType +case object SelfLoop extends CyclicRegionType +case object WhileLoop extends CyclicRegionType +case object NaturalLoop extends CyclicRegionType +case object Improper extends CyclicRegionType + +case class Region(regionType: RegionType, nodeIds: Set[Int]) { + + override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")})" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala similarity index 60% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index efa476947c..17b03de82a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -4,159 +4,21 @@ package tac package fpcf package analyses package string -package preprocessing +package flowanalysis import scala.collection.mutable -import org.opalj.br.cfg.BasicBlock -import org.opalj.br.cfg.CFG import org.opalj.graphs.DominatorTree -import scalax.collection.OneOrMore import scalax.collection.edges.DiEdge -import scalax.collection.generic.Edge -import scalax.collection.hyperedges.DiHyperEdge import scalax.collection.immutable.Graph -import scalax.collection.io.dot.DotAttr -import scalax.collection.io.dot.DotAttrStmt -import scalax.collection.io.dot.DotEdgeStmt -import scalax.collection.io.dot.DotGraph -import scalax.collection.io.dot.DotNodeStmt -import scalax.collection.io.dot.DotRootGraph -import scalax.collection.io.dot.Elem -import scalax.collection.io.dot.Graph2DotExport -import scalax.collection.io.dot.Id -import scalax.collection.io.dot.NodeId - -trait RegionType extends Product - -trait AcyclicRegionType extends RegionType -trait CyclicRegionType extends RegionType - -case object Block extends AcyclicRegionType -case object IfThen extends AcyclicRegionType -case object IfThenElse extends AcyclicRegionType -case object Case extends AcyclicRegionType -case object Proper extends AcyclicRegionType -case object SelfLoop extends CyclicRegionType -case object WhileLoop extends CyclicRegionType -case object NaturalLoop extends CyclicRegionType -case object Improper extends CyclicRegionType - -case class Region(regionType: RegionType, nodeIds: Set[Int]) { - - override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")})" -} /** * @author Maximilian Rüsch */ -class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { - type SGraph = Graph[Region, DiEdge[Region]] - type ControlTree = Graph[Region, DiEdge[Region]] - - val graph: SGraph = { - val edges = cfg.allNodes.flatMap { - case bb: BasicBlock => - val firstNode = Region(Block, Set(bb.startPC)) - var currentEdges = Seq.empty[DiEdge[Region]] - if (bb.startPC != bb.endPC) { - Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrPC => - currentEdges :+= DiEdge( - currentEdges.lastOption.map(_.target).getOrElse(firstNode), - Region(Block, Set(instrPC)) - ) - } - } - - val lastNode = if (currentEdges.nonEmpty) currentEdges.last.target - else firstNode - currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Region(Block, Set(s.nodeId)))) - case n => - n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) - }.toSet - val g = Graph.from(edges) - - val normalReturnNode = Region(Block, Set(cfg.normalReturnNode.nodeId)) - val abnormalReturnNode = Region(Block, Set(cfg.abnormalReturnNode.nodeId)) - val hasNormalReturn = cfg.normalReturnNode.predecessors.nonEmpty - val hasAbnormalReturn = cfg.abnormalReturnNode.predecessors.nonEmpty - - (hasNormalReturn, hasAbnormalReturn) match { - case (true, true) => - val allReturnNode = Region(Block, Set(-42)) - g.incl(DiEdge(normalReturnNode, allReturnNode)).incl(DiEdge(abnormalReturnNode, allReturnNode)) - - case (true, false) => - g.excl(abnormalReturnNode) - - case (false, true) => - g.excl(normalReturnNode) - - case _ => - throw new IllegalStateException("Cannot transform a CFG with neither normal nor abnormal return edges!") - } - } - val entry: Region = Region(Block, Set(cfg.startBlock.nodeId)) - - def graphDot[N <: Region, E <: Edge[N]](graph: Graph[N, E]): String = { - val root = DotRootGraph( - directed = true, - id = Some(Id("MyDot")), - attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr(Id("shape"), Id("record"))))), - attrList = List(DotAttr(Id("attr_1"), Id(""""one"""")), DotAttr(Id("attr_2"), Id(""))) - ) - - def edgeTransformer(innerEdge: Graph[N, E]#EdgeT): Option[(DotGraph, DotEdgeStmt)] = { - val edge = innerEdge.outer - Some( - ( - root, - DotEdgeStmt(NodeId(edge.sources.head.toString), NodeId(edge.targets.head.toString)) - ) - ) - } - - def hEdgeTransformer(innerHEdge: Graph[N, E]#EdgeT): Iterable[(DotGraph, DotEdgeStmt)] = { - val color = DotAttr(Id("color"), Id(s""""#%06x"""".format(scala.util.Random.nextInt(1 << 24)))) - - innerHEdge.outer.targets.toList map (target => - ( - root, - DotEdgeStmt( - NodeId(innerHEdge.outer.sources.head.toString), - NodeId(target.toString), - Seq(color) - ) - ) - ) - } - - def nodeTransformer(innerNode: Graph[N, E]#NodeT): Option[(DotGraph, DotNodeStmt)] = { - val node = innerNode.outer - val attributes = if (node.nodeIds.size == 1) Seq.empty - else Seq( - DotAttr(Id("style"), Id("filled")), - DotAttr(Id("fillcolor"), Id("\"green\"")) - ) - Some( - ( - root, - DotNodeStmt(NodeId(node.toString), attributes) - ) - ) - } - - graph.toDot( - root, - edgeTransformer, - hEdgeTransformer = Some(hEdgeTransformer), - cNodeTransformer = Some(nodeTransformer), - iNodeTransformer = Some(nodeTransformer) - ) - } +object StructuralAnalysis { - def analyze(graph: SGraph, entry: Region): (Graph[Region, DiEdge[Region]], Graph[Region, DiEdge[Region]]) = { + def analyze(graph: FlowGraph, entry: Region): (FlowGraph, ControlTree) = { var g = graph var curEntry = entry var controlTree = Graph.empty[Region, DiEdge[Region]] @@ -167,9 +29,9 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { var postCtr = 1 val post = mutable.ListBuffer.empty[Region] - def replace(g: SGraph, subRegions: Set[Region], regionType: RegionType): (SGraph, Region) = { + def replace(g: FlowGraph, subRegions: Set[Region], regionType: RegionType): (FlowGraph, Region) = { val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) - var newGraph: SGraph = g + var newGraph: FlowGraph = g // Compact newGraph = newGraph.incl(newRegion) @@ -197,14 +59,14 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { (newGraph, newRegion) } - PostOrderTraversal.foreachInTraversalFrom[Region, SGraph](g, curEntry)(post.append) { (x, y) => + PostOrderTraversal.foreachInTraversalFrom[Region, FlowGraph](g, curEntry)(post.append) { (x, y) => x.nodeIds.head.compare(y.nodeIds.head) } while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) - val (newStartingNode, acyclicRegionOpt) = AcyclicRegionType.locate(g, n) + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes) = acyclicRegionOpt.get @@ -243,7 +105,7 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { reachUnder = reachUnder.incl(m) } - val cyclicRegionOpt = CyclicRegionType.locate(g, n, reachUnder) + val cyclicRegionOpt = locateCyclicRegion(g, n, reachUnder) if (cyclicRegionOpt.isDefined) { val (crType, nodes) = cyclicRegionOpt.get @@ -270,31 +132,6 @@ class StructuralAnalysis(cfg: CFG[Stmt[V], TACStmts[V]]) { (g, controlTree) } - def combine(cfg: SGraph, controlTree: SGraph): Graph[Region, Edge[Region]] = { - var combinedGraph = cfg - .++[Region, DiEdge[Region]](controlTree.nodes.map(_.outer), Iterable.empty) - .asInstanceOf[Graph[Region, Edge[Region]]] - - for { - iNode <- controlTree.nodes - nodes = combinedGraph.nodes.filter((n: Graph[Region, Edge[Region]]#NodeT) => - n.outer.nodeIds.subsetOf(iNode.outer.nodeIds) - ).map(_.outer) - actualSubsetNodes = nodes.filter(n => n.nodeIds != iNode.outer.nodeIds) - remainingNodes = actualSubsetNodes.filter(n => - !actualSubsetNodes.exists(nn => n.nodeIds != nn.nodeIds && n.nodeIds.subsetOf(nn.nodeIds)) - ) - if remainingNodes.size > 1 - } { - combinedGraph = combinedGraph.incl(DiHyperEdge(OneOrMore(iNode.outer), OneOrMore.from(remainingNodes).get)) - } - - combinedGraph - } -} - -object StructuralAnalysis { - private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexedNodes: Seq[A], domTree: DominatorTree)( m: A, n: A @@ -312,31 +149,11 @@ object StructuralAnalysis { } } } -} - -object PostOrderTraversal { - - private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( - graph: G, - toVisit: Seq[A], - visited: Set[A] - )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { - if (toVisit.nonEmpty) { - val next = toVisit.head - val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toList.sorted - - foreachInTraversal(graph, nextSuccessors ++ toVisit.tail, visited + next)(nodeHandler) - nodeHandler(next) - } - } - def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( - implicit ordering: Ordering[A] - ): Unit = foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) -} - -object AcyclicRegionType { - def locate[A, G <: Graph[A, DiEdge[A]]](graph: G, startingNode: A): (A, Option[(AcyclicRegionType, Set[A])]) = { + private def locateAcyclicRegion[A, G <: Graph[A, DiEdge[A]]]( + graph: G, + startingNode: A + ): (A, Option[(AcyclicRegionType, Set[A])]) = { var nSet = Set.empty[A] // Expand nSet down @@ -438,11 +255,8 @@ object AcyclicRegionType { (newStartingNode, rType.map((_, nSet))) } -} -object CyclicRegionType { - - def locate[A, G <: Graph[A, DiEdge[A]]]( + private def locateCyclicRegion[A, G <: Graph[A, DiEdge[A]]]( graph: G, startingNode: A, reachUnder: Set[A] @@ -468,3 +282,24 @@ object CyclicRegionType { } } } + +object PostOrderTraversal { + + private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( + graph: G, + toVisit: Seq[A], + visited: Set[A] + )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { + if (toVisit.nonEmpty) { + val next = toVisit.head + val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toList.sorted + + foreachInTraversal(graph, nextSuccessors ++ toVisit.tail, visited + next)(nodeHandler) + nodeHandler(next) + } + } + + def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( + implicit ordering: Ordering[A] + ): Unit = foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala new file mode 100644 index 0000000000..60784aedb2 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -0,0 +1,164 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string + +import org.opalj.br.cfg.BasicBlock +import org.opalj.br.cfg.CFG + +import scalax.collection.OneOrMore +import scalax.collection.edges.DiEdge +import scalax.collection.generic.Edge +import scalax.collection.hyperedges.DiHyperEdge +import scalax.collection.immutable.Graph +import scalax.collection.immutable.TypedGraphFactory +import scalax.collection.io.dot.DotAttr +import scalax.collection.io.dot.DotAttrStmt +import scalax.collection.io.dot.DotEdgeStmt +import scalax.collection.io.dot.DotGraph +import scalax.collection.io.dot.DotNodeStmt +import scalax.collection.io.dot.DotRootGraph +import scalax.collection.io.dot.Elem +import scalax.collection.io.dot.Graph2DotExport +import scalax.collection.io.dot.Id +import scalax.collection.io.dot.NodeId + +/** + * @author Maximilian Rüsch + */ +package object flowanalysis { + + type ControlTree = Graph[Region, DiEdge[Region]] + type FlowGraph = Graph[Region, DiEdge[Region]] + + object FlowGraph extends TypedGraphFactory[Region, DiEdge[Region]] { + + def apply[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): FlowGraph = { + val edges = cfg.allNodes.flatMap { + case bb: BasicBlock => + val firstNode = Region(Block, Set(bb.startPC)) + var currentEdges = Seq.empty[DiEdge[Region]] + if (bb.startPC != bb.endPC) { + Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrPC => + currentEdges :+= DiEdge( + currentEdges.lastOption.map(_.target).getOrElse(firstNode), + Region(Block, Set(instrPC)) + ) + } + } + + val lastNode = if (currentEdges.nonEmpty) currentEdges.last.target + else firstNode + currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Region(Block, Set(s.nodeId)))) + case n => + n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) + }.toSet + val g = Graph.from(edges) + + val normalReturnNode = Region(Block, Set(cfg.normalReturnNode.nodeId)) + val abnormalReturnNode = Region(Block, Set(cfg.abnormalReturnNode.nodeId)) + val hasNormalReturn = cfg.normalReturnNode.predecessors.nonEmpty + val hasAbnormalReturn = cfg.abnormalReturnNode.predecessors.nonEmpty + + (hasNormalReturn, hasAbnormalReturn) match { + case (true, true) => + val allReturnNode = Region(Block, Set(-42)) + g.incl(DiEdge(normalReturnNode, allReturnNode)).incl(DiEdge(abnormalReturnNode, allReturnNode)) + + case (true, false) => + g.excl(abnormalReturnNode) + + case (false, true) => + g.excl(normalReturnNode) + + case _ => + throw new IllegalStateException( + "Cannot transform a CFG with neither normal nor abnormal return edges!" + ) + } + } + + def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Region = Region(Block, Set(cfg.startBlock.nodeId)) + + def toDot[N <: Region, E <: Edge[N]](graph: Graph[N, E]): String = { + val root = DotRootGraph( + directed = true, + id = Some(Id("MyDot")), + attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr(Id("shape"), Id("record"))))), + attrList = List(DotAttr(Id("attr_1"), Id(""""one"""")), DotAttr(Id("attr_2"), Id(""))) + ) + + def edgeTransformer(innerEdge: Graph[N, E]#EdgeT): Option[(DotGraph, DotEdgeStmt)] = { + val edge = innerEdge.outer + Some( + ( + root, + DotEdgeStmt(NodeId(edge.sources.head.toString), NodeId(edge.targets.head.toString)) + ) + ) + } + + def hEdgeTransformer(innerHEdge: Graph[N, E]#EdgeT): Iterable[(DotGraph, DotEdgeStmt)] = { + val color = DotAttr(Id("color"), Id(s""""#%06x"""".format(scala.util.Random.nextInt(1 << 24)))) + + innerHEdge.outer.targets.toList map (target => + ( + root, + DotEdgeStmt( + NodeId(innerHEdge.outer.sources.head.toString), + NodeId(target.toString), + Seq(color) + ) + ) + ) + } + + def nodeTransformer(innerNode: Graph[N, E]#NodeT): Option[(DotGraph, DotNodeStmt)] = { + val node = innerNode.outer + val attributes = if (node.nodeIds.size == 1) Seq.empty + else Seq( + DotAttr(Id("style"), Id("filled")), + DotAttr(Id("fillcolor"), Id("\"green\"")) + ) + Some( + ( + root, + DotNodeStmt(NodeId(node.toString), attributes) + ) + ) + } + + graph.toDot( + root, + edgeTransformer, + hEdgeTransformer = Some(hEdgeTransformer), + cNodeTransformer = Some(nodeTransformer), + iNodeTransformer = Some(nodeTransformer) + ) + } + + def enrichWithControlTree(flowGraph: FlowGraph, controlTree: ControlTree): Graph[Region, Edge[Region]] = { + var combinedGraph = flowGraph + .++(controlTree.nodes.map(_.outer), Iterable.empty) + .asInstanceOf[Graph[Region, Edge[Region]]] + + for { + node <- controlTree.nodes.toOuter.asInstanceOf[Set[Region]] + nodes = combinedGraph.nodes.filter((n: Graph[Region, Edge[Region]]#NodeT) => + n.outer.nodeIds.subsetOf(node.nodeIds) + ).map(_.outer) + actualSubsetNodes = nodes.filter(n => n.nodeIds != node.nodeIds) + remainingNodes = actualSubsetNodes.filter(n => + !actualSubsetNodes.exists(nn => n.nodeIds != nn.nodeIds && n.nodeIds.subsetOf(nn.nodeIds)) + ) + if remainingNodes.size > 1 + } { + combinedGraph = combinedGraph.incl(DiHyperEdge(OneOrMore(node), OneOrMore.from(remainingNodes).get)) + } + + combinedGraph + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala index 48f5a0d82f..a80cea3a77 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler object PathFinder { @@ -32,17 +33,13 @@ object PathFinder { } def findPath(value: V, tac: TAC): Option[Path] = { - val structural = new StructuralAnalysis(tac.cfg) - - if (structural.graph.isCyclic || value.definedBy.size > 1) { + if (FlowGraph(tac.cfg).isCyclic || value.definedBy.size > 1) { return None; } val defSite = value.definedBy.head val allDefAndUseSites = tac.stmts(defSite).asAssignment.targetVar.usedBy.+(defSite).toList.sorted - // val (_, _) = structural.analyze(structural.graph, Region(Block, Set(tac.cfg.startBlock.nodeId))) - Some(Path(allDefAndUseSites.map(PathElement.apply(_)(tac.stmts)))) } From 177aea797b3131324c859662388a99df656cd294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 11:41:56 +0200 Subject: [PATCH 416/583] Simplify ayclic region detection --- .../flowanalysis/StructuralAnalysis.scala | 76 ++++++++----------- 1 file changed, 30 insertions(+), 46 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 17b03de82a..b9b65aee25 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -154,54 +154,38 @@ object StructuralAnalysis { graph: G, startingNode: A ): (A, Option[(AcyclicRegionType, Set[A])]) = { - var nSet = Set.empty[A] + var nSet = Set.empty[graph.NodeT] // Expand nSet down - var n = startingNode - var p = true - var s = graph.get(n).diSuccessors.size == 1 // TODO refactor into own node type and `hasSingleSuccessor` / `getSingleSuccessor once running - while (p & s) { + var n = graph.get(startingNode) + while ((n.outer == startingNode || n.diPredecessors.size == 1) && n.diSuccessors.size == 1) { nSet += n - n = graph.get(n).diSuccessors.head.outer - p = graph.get(n).diPredecessors.size == 1 - s = graph.get(n).diSuccessors.size == 1 + n = n.diSuccessors.head } - if (p) { + if (n.diPredecessors.size == 1) { nSet += n } // Expand nSet up - n = startingNode - p = graph.get(n).diPredecessors.size == 1 - s = true - while (p & s) { + n = graph.get(startingNode) + while (n.diPredecessors.size == 1 && (n.outer == startingNode || n.diSuccessors.size == 1)) { nSet += n - n = graph.get(n).diPredecessors.head.outer - p = graph.get(n).diPredecessors.size == 1 - s = graph.get(n).diSuccessors.size == 1 + n = n.diPredecessors.head } - if (s) { + if (n.diSuccessors.size == 1) { nSet += n } - val newStartingNode = n - val newDirectSuccessors = graph.get(newStartingNode).diSuccessors.map(_.outer) - def locateProperAcyclicInterval: Option[AcyclicRegionType] = { - assert(newDirectSuccessors.size > 1, "Detection for single direct successors should have already run!") - var currentNodeSet = Set(n) - var currentSuccessors = graph.get(n).diSuccessors.map(_.outer) - while (currentSuccessors.size > 1 && graph.filter(nodeP = - node => currentNodeSet.contains(node.outer) - ).isAcyclic - ) { + var currentSuccessors = n.diSuccessors + while (currentSuccessors.size > 1 && graph.filter(node => currentNodeSet.contains(node)).isAcyclic) { currentNodeSet = currentNodeSet ++ currentSuccessors - currentSuccessors = currentSuccessors.flatMap(node => graph.get(node).diSuccessors.map(_.outer)) + currentSuccessors = currentSuccessors.flatMap(node => node.diSuccessors) } - val allPredecessors = currentNodeSet.excl(n).flatMap(node => graph.get(node).diPredecessors.map(_.outer)) - if (graph.filter(nodeP = node => currentNodeSet.contains(node.outer)).isCyclic) { + val allPredecessors = currentNodeSet.excl(n).flatMap(node => node.diPredecessors) + if (graph.filter(node => currentNodeSet.contains(node)).isCyclic) { None } else if (!allPredecessors.equals(currentNodeSet.diff(currentSuccessors))) { None @@ -212,6 +196,7 @@ object StructuralAnalysis { } } + val newDirectSuccessors = n.diSuccessors val rType = if (nSet.size > 1) { // Condition is added to ensure chosen bb does not contain any self loops or other cyclic stuff // IMPROVE weaken to allow back edges from the "last" nSet member to the first to enable reductions to self loops @@ -222,38 +207,37 @@ object StructuralAnalysis { } else if (newDirectSuccessors.size == 2) { val m = newDirectSuccessors.head val k = newDirectSuccessors.tail.head - if (graph.get(m).diSuccessors.headOption == graph.get(k).diSuccessors.headOption - && graph.get(m).diSuccessors.size == 1 - && graph.get(m).diPredecessors.size == 1 - && graph.get(k).diPredecessors.size == 1 + if (m.diSuccessors.headOption == k.diSuccessors.headOption + && m.diSuccessors.size == 1 + && m.diPredecessors.size == 1 + && k.diPredecessors.size == 1 ) { - nSet = Set(newStartingNode, m, k) + nSet = Set(n, m, k) Some(IfThenElse) } else if (( - graph.get(m).diSuccessors.size == 1 - && graph.get(m).diSuccessors.head.outer == k - && graph.get(m).diPredecessors.size == 1 - && graph.get(k).diPredecessors.size == 2 + m.diSuccessors.size == 1 + && m.diSuccessors.head == k + && m.diPredecessors.size == 1 + && k.diPredecessors.size == 2 ) || ( - graph.get(k).diSuccessors.size == 1 - && graph.get(k).diSuccessors.head.outer == m - && graph.get(k).diPredecessors.size == 1 - && graph.get(m).diPredecessors.size == 2 + k.diSuccessors.size == 1 + && k.diSuccessors.head == m + && k.diPredecessors.size == 1 + && m.diPredecessors.size == 2 ) ) { - nSet = Set(newStartingNode, m, k) + nSet = Set(n, m, k) Some(IfThen) } else { locateProperAcyclicInterval } } else if (newDirectSuccessors.size > 2) { - // TODO implement Case as well locateProperAcyclicInterval } else { None } - (newStartingNode, rType.map((_, nSet))) + (n.outer, rType.map((_, nSet.map(_.outer)))) } private def locateCyclicRegion[A, G <: Graph[A, DiEdge[A]]]( From 152fa7b0d503ff636e98416739b201d897ba4256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 11:51:44 +0200 Subject: [PATCH 417/583] Improve node coloring for dot export --- .../tac/fpcf/analyses/string/flowanalysis/Region.scala | 6 +++--- .../tac/fpcf/analyses/string/flowanalysis/package.scala | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala index 79e928c478..93f5141be1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala @@ -6,10 +6,10 @@ package analyses package string package flowanalysis -trait RegionType extends Product +sealed trait RegionType extends Product -trait AcyclicRegionType extends RegionType -trait CyclicRegionType extends RegionType +sealed trait AcyclicRegionType extends RegionType +sealed trait CyclicRegionType extends RegionType case object Block extends AcyclicRegionType case object IfThen extends AcyclicRegionType diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index 60784aedb2..2abbac0f76 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -120,7 +120,13 @@ package object flowanalysis { val attributes = if (node.nodeIds.size == 1) Seq.empty else Seq( DotAttr(Id("style"), Id("filled")), - DotAttr(Id("fillcolor"), Id("\"green\"")) + DotAttr( + Id("fillcolor"), + node.regionType match { + case _: AcyclicRegionType => Id(""""green"""") + case _: CyclicRegionType => Id(""""purple"""") + } + ) ) Some( ( From 58b2f7d805028464238a3a81b2fbca01e0043944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 11:56:25 +0200 Subject: [PATCH 418/583] Clarify variable reference --- .../analyses/string/flowanalysis/StructuralAnalysis.scala | 4 ++-- .../opalj/tac/fpcf/analyses/string/flowanalysis/package.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index b9b65aee25..917bacd7fd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -29,9 +29,9 @@ object StructuralAnalysis { var postCtr = 1 val post = mutable.ListBuffer.empty[Region] - def replace(g: FlowGraph, subRegions: Set[Region], regionType: RegionType): (FlowGraph, Region) = { + def replace(currentGraph: FlowGraph, subRegions: Set[Region], regionType: RegionType): (FlowGraph, Region) = { val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) - var newGraph: FlowGraph = g + var newGraph: FlowGraph = currentGraph // Compact newGraph = newGraph.incl(newRegion) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index 2abbac0f76..bdd4a76e29 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -124,7 +124,7 @@ package object flowanalysis { Id("fillcolor"), node.regionType match { case _: AcyclicRegionType => Id(""""green"""") - case _: CyclicRegionType => Id(""""purple"""") + case _: CyclicRegionType => Id(""""purple"""") } ) ) From 759c242d6b72679cbfb30e876d77bd3c37448b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 23:04:13 +0200 Subject: [PATCH 419/583] Overhaul string analysis computation for flow functions --- .../string/StringConstancyInformation.scala | 31 -- .../properties/string/StringTreeNode.scala | 32 ++ .../analyses/string/ComputationState.scala | 10 + .../fpcf/analyses/string/EPSDepender.scala | 26 -- .../fpcf/analyses/string/StringAnalysis.scala | 38 +- .../analyses/string/StringInterpreter.scala | 107 ++--- .../BinaryExprInterpreter.scala | 27 +- .../InterpretationHandler.scala | 73 ++-- .../SimpleValueConstExprInterpreter.scala | 31 +- .../L0ArrayAccessInterpreter.scala | 78 ---- .../L0FunctionCallInterpreter.scala | 93 ++-- .../L0InterpretationHandler.scala | 62 ++- .../L0NewArrayInterpreter.scala | 74 ---- .../L0NonVirtualFunctionCallInterpreter.scala | 21 +- .../L0NonVirtualMethodCallInterpreter.scala | 50 +-- .../L0StaticFunctionCallInterpreter.scala | 92 ++-- .../L0VirtualFunctionCallInterpreter.scala | 407 +++++------------- .../L0VirtualMethodCallInterpreter.scala | 24 +- .../L1FieldReadInterpreter.scala | 74 ++-- .../L1InterpretationHandler.scala | 72 ++-- .../L1VirtualFunctionCallInterpreter.scala | 56 +-- .../analyses/string/string_analysis.scala | 2 + .../string/StringFlowFunction.scala | 54 +++ .../string/StringTreeEnvironment.scala | 30 ++ 24 files changed, 619 insertions(+), 945 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala index 7b3da8b616..d0ef451d9d 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala @@ -13,8 +13,6 @@ trait StringConstancyInformation { def treeFn: StringTreeNode => StringTreeNode def tree: StringTreeNode - def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation - final def isTheNeutralElement: Boolean = tree.isNeutralElement final def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel final def toRegex: String = tree.toRegex @@ -23,48 +21,19 @@ trait StringConstancyInformation { case class StringConstancyInformationConst(override val tree: StringTreeNode) extends StringConstancyInformation { override def treeFn: StringTreeNode => StringTreeNode = _ => tree - - override def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { - StringConstancyInformationConst(tree.replaceParameters(parameters.map { - case (index, sci) => (index, sci.tree) - })) - } } case class StringConstancyInformationFunction(override val treeFn: StringTreeNode => StringTreeNode) extends StringConstancyInformation { override def tree: StringTreeNode = treeFn(StringTreeNeutralElement) - - override def replaceParameters(parameters: Map[Int, StringConstancyInformation]): StringConstancyInformation = { - StringConstancyInformationFunction((pv: StringTreeNode) => - treeFn(pv).replaceParameters(parameters.map { - case (index, sci) => (index, sci.tree) - }) - ) - } } object StringConstancyInformation { - def reduceMultiple(scis: Seq[StringConstancyInformation]): StringConstancyInformation = { - val relScis = scis.filter(!_.isTheNeutralElement) - relScis.size match { - case 0 => neutralElement - case 1 => relScis.head - case _ if relScis.forall(_.isInstanceOf[StringConstancyInformationConst]) => - StringConstancyInformationConst(StringTreeOr(relScis.map(_.tree))) - case _ => - StringConstancyInformationFunction((pv: StringTreeNode) => StringTreeOr(relScis.map(_.treeFn(pv)))) - } - } - def lb: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicString) def ub: StringConstancyInformation = StringConstancyInformationConst(StringTreeNeutralElement) - def dynamicInt: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicInt) - def dynamicFloat: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicFloat) - def neutralElement: StringConstancyInformation = StringConstancyInformationConst(StringTreeNeutralElement) def nullElement: StringConstancyInformation = StringConstancyInformationConst(StringTreeNull) def getElementForParameterPC(paramPC: Int): StringConstancyInformation = { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index a5b21f2aff..42da377ee4 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -26,6 +26,18 @@ sealed trait StringTreeNode { def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode def isNeutralElement: Boolean = false + def isInvalidElement: Boolean = false +} + +object StringTreeNode { + + def reduceMultiple(trees: Seq[StringTreeNode]): StringTreeNode = { + if (trees.size == 1) trees.head + else if (trees.exists(_.isInvalidElement)) StringTreeInvalidElement + else StringTreeOr(trees) + } + + def lb: StringTreeNode = StringTreeDynamicString } case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { @@ -59,6 +71,7 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } override def simplify: StringTreeNode = { + // TODO neutral concat something is always neutral val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) if (nonNeutralChildren.isEmpty) StringTreeNeutralElement @@ -183,6 +196,17 @@ case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC } +object StringTreeParameter { + + def forParameterPC(paramPC: Int): StringTreeParameter = { + if (paramPC >= -1) { + throw new IllegalArgumentException(s"Invalid parameter pc given: $paramPC") + } + // Parameters start at PC -2 downwards + StringTreeParameter(Math.abs(paramPC + 2)) + } +} + object StringTreeNeutralElement extends SimpleStringTreeNode { override def toRegex: String = "" @@ -191,6 +215,14 @@ object StringTreeNeutralElement extends SimpleStringTreeNode { override def isNeutralElement: Boolean = true } +object StringTreeInvalidElement extends SimpleStringTreeNode { + override def toRegex: String = throw new UnsupportedOperationException() + + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + + override def isInvalidElement: Boolean = true +} + object StringTreeNull extends SimpleStringTreeNode { // Using this element nested in some other element might lead to unexpected results... override def toRegex: String = "^null$" diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index f28b734cc0..9084f91705 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -39,3 +39,13 @@ case class ComputationState(dm: DefinedMethod, entity: SContext) { } case class DUSiteState(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) + +private[string] case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { + + def tac: TAC = { + if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) + tacDependee.ub.tac.get + else + throw new IllegalStateException("Cannot get a tac from a TACAI with no or empty upper bound!") + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala deleted file mode 100644 index 9af0d6d24e..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/EPSDepender.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string - -import org.opalj.fpcf.SomeEOptionP - -/** - * @author Maximilian Rüsch - */ -private[string] case class EPSDepender[T <: ASTNode[V]]( - instr: T, - pc: Int, - state: DUSiteState, - dependees: Seq[SomeEOptionP] -) { - - def withDependees(newDependees: Seq[SomeEOptionP]): EPSDepender[T] = EPSDepender( - instr, - pc, - state, - newDependees - ) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index e87001b881..a8e694ed6c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -5,6 +5,8 @@ package fpcf package analyses package string +import org.opalj.ai.FormalParametersOriginOffset +import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.ObjectType @@ -19,7 +21,7 @@ import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeOr -import org.opalj.fpcf.Entity +import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult @@ -33,10 +35,10 @@ import org.opalj.tac.fpcf.analyses.string.preprocessing.Path import org.opalj.tac.fpcf.analyses.string.preprocessing.PathElement import org.opalj.tac.fpcf.analyses.string.preprocessing.PathFinder import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunction /** - * String Analysis trait defining some basic dependency handling. - * * @author Maximilian Rüsch */ trait StringAnalysis extends FPCFAnalysis { @@ -70,8 +72,23 @@ trait StringAnalysis extends FPCFAnalysis { val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) val defSites = uVar.definedBy.toArray.sorted + // TODO put a function parameter with their parameter string tree into the flow analysis // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { + // TODO what do we do with string builder parameters? + if (pc <= FormalParametersOriginOffset) { + if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { + return Result(FinalEP(InterpretationHandler.getEntity(state), sff)) + + return StringInterpreter.computeFinalLBFor(state.entity._1) + } else { + return StringInterpreter.computeFinalResult(ConstantResultFlow.forVariable( + state.entity._1, + StringTreeParameter.forParameterPC(pc) + )) + } + } + val ep = ps( InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac, state.entity._1), StringConstancyProperty.key @@ -249,6 +266,7 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), + PropertyBounds.ub(StringFlowFunction), PropertyBounds.lub(StringConstancyProperty) ) @@ -265,16 +283,10 @@ trait LazyStringAnalysis extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation( - StringConstancyProperty.key, - (e: Entity) => { - e match { - case _: (_, _) => initData._1.analyze(e.asInstanceOf[SContext]) - case entity: DUSiteEntity => initData._2.analyze(entity) - case _ => throw new IllegalArgumentException(s"Unexpected entity passed for string analysis: $e") - } - } - ) + // TODO double register lazy computation for pc scoped entities as well + ps.registerLazyPropertyComputation(StringConstancyProperty.key, initData._1.analyze) + ps.registerLazyPropertyComputation(StringFlowFunction.key, initData._2.analyze) + initData._1 } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 74d7ec6fb3..210e11f48f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -5,98 +5,79 @@ package fpcf package analyses package string -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -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.fpcf.SomeFinalEP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.string.StringFlowFunction /** * @author Maximilian Rüsch */ trait StringInterpreter { - type T <: ASTNode[V] + type T <: Stmt[V] /** * @param instr The instruction that is to be interpreted. - * @param pc The pc that corresponds to the given instruction. * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given * instruction. */ - def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult - - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DUSiteState): Result = - StringInterpreter.computeFinalResult(pc, sci) - - // IMPROVE remove this since awaiting all final is not really feasible - // replace with intermediate lattice result approach - protected final def awaitAllFinalContinuation( - depender: EPSDepender[T], - finalResult: Seq[SomeFinalEP] => ProperPropertyComputationResult - )(result: SomeEPS): ProperPropertyComputationResult = { - if (result.isFinal) { - val updatedDependees = depender.dependees.updated( - depender.dependees.indexWhere(_.e.asInstanceOf[Entity] == result.e.asInstanceOf[Entity]), - result - ) - - if (updatedDependees.forall(_.isFinal)) { - finalResult(updatedDependees.asInstanceOf[Seq[SomeFinalEP]]) - } else { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(depender.pc)(depender.state), - StringConstancyProperty.ub, - depender.dependees.filter(_.isRefinable).toSet, - awaitAllFinalContinuation(depender.withDependees(updatedDependees), finalResult) - ) - } - } else { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(depender.pc)(depender.state), - StringConstancyProperty.ub, - depender.dependees.filter(_.isRefinable).toSet, - awaitAllFinalContinuation(depender, finalResult) - ) - } - } + def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult + + def computeFinalLBFor(v: V)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalLBFor(v) + + def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalLBFor(v) + + def computeFinalResult(sff: StringFlowFunction)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalResult(sff) } object StringInterpreter { - def computeFinalResult(pc: Int, sci: StringConstancyInformation)(implicit state: DUSiteState): Result = - Result(FinalEP( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty(sci) - )) + def computeFinalLBFor(v: V)(implicit state: InterpretationState): Result = + computeFinalLBFor(v.toPersistentForm(state.tac.stmts)) + + def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = + computeFinalResult(StringFlowFunction.lb(v)) + + def computeFinalResult(sff: StringFlowFunction)(implicit state: InterpretationState): Result = + Result(FinalEP(InterpretationHandler.getEntity(state), sff)) } trait ParameterEvaluatingStringInterpreter extends StringInterpreter { - val ps: PropertyStore - - protected def getParametersForPC(pc: Int)(implicit state: DUSiteState): Seq[Expr[V]] = { + protected def getParametersForPC(pc: Int)(implicit state: InterpretationState): Seq[Expr[V]] = { state.tac.stmts(state.tac.pcToIndex(pc)) match { case ExprStmt(_, vfc: FunctionCall[V]) => vfc.params case Assignment(_, _, fc: FunctionCall[V]) => fc.params case _ => Seq.empty } } +} - protected def evaluateParameters(params: Seq[Expr[V]])(implicit - state: DUSiteState - ): Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = { - params.map { nextParam => - Seq.from(nextParam.asVar.definedBy.toArray.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) - }) - } - } +trait AssignmentLikeBasedStringInterpreter extends StringInterpreter { + + type E <: Expr[V] + + override type T <: AssignmentLikeStmt[V] + + override final def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = + interpretExpr(instr, instr.expr.asInstanceOf[E]) + + def interpretExpr(instr: T, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult +} + +trait AssignmentBasedStringInterpreter extends AssignmentLikeBasedStringInterpreter { + + override type T = Assignment[V] + + override final def interpretExpr(instr: T, expr: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = + interpretExpr(instr.targetVar, expr) + + def interpretExpr(target: V, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala index 28182ce4a7..e461082ab8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala @@ -8,15 +8,18 @@ package interpretation import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt -import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat +import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.IdentityFlow /** * @author Maximilian Rüsch */ -object BinaryExprInterpreter extends StringInterpreter { +object BinaryExprInterpreter extends AssignmentBasedStringInterpreter { - override type T = BinaryExpr[V] + override type E = BinaryExpr[V] /** * Currently, this implementation supports the interpretation of the following binary expressions: @@ -24,14 +27,16 @@ object BinaryExprInterpreter extends StringInterpreter { *

  • [[ComputationalTypeInt]] *
  • [[ComputationalTypeFloat]]
  • * - * For all other expressions, a [[StringConstancyInformation.neutralElement]] will be returned. + * For all other expressions, [[IdentityFlow]] will be returned. */ - def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val sci = instr.cTpe match { - case ComputationalTypeInt => StringConstancyInformation.dynamicInt - case ComputationalTypeFloat => StringConstancyInformation.dynamicFloat - case _ => StringConstancyInformation.neutralElement - } - computeFinalResult(pc, sci) + override def interpretExpr(target: V, expr: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val pt = target.toPersistentForm(state.tac.stmts) + computeFinalResult(expr.cTpe match { + case ComputationalTypeInt => ConstantResultFlow.forVariable(pt, StringTreeDynamicInt) + case ComputationalTypeFloat => ConstantResultFlow.forVariable(pt, StringTreeDynamicFloat) + case _ => IdentityFlow + }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index b66c341ac2..60234a2ca8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -6,11 +6,17 @@ package analyses package string package interpretation -import org.opalj.ai.FormalParametersOriginOffset -import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod -import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunction /** * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site @@ -23,37 +29,52 @@ import org.opalj.fpcf.ProperPropertyComputationResult */ abstract class InterpretationHandler { - def analyze(entity: DUSiteEntity): ProperPropertyComputationResult = { - val pc = entity.pc - implicit val duSiteState: DUSiteState = DUSiteState(pc, entity.dm, entity.tac, entity.entity) - if (pc <= FormalParametersOriginOffset) { - if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) - } else { - return StringInterpreter.computeFinalResult(pc, StringConstancyInformation.getElementForParameterPC(pc)) - } + def ps: PropertyStore + + def analyze(entity: MethodPC): ProperPropertyComputationResult = { + val tacaiEOptP = ps(entity.dm.definedMethod, TACAI.key) + implicit val state: InterpretationState = InterpretationState(entity.pc, entity.dm, tacaiEOptP) + + if (tacaiEOptP.isRefinable) { + InterimResult.forUB( + InterpretationHandler.getEntity, + StringFlowFunction.ub, + Set(state.tacDependee), + continuation(state) + ) + } else if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + StringInterpreter.computeFinalResult(ConstantResultFlow.forAll(StringTreeNode.lb)) + } else { + processNew } + } + + private def continuation(state: InterpretationState)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case finalEP: FinalEP[Method, TACAI] if + eps.pk.equals(TACAI.key) => + state.tacDependee = finalEP + processNew(state) - processNewPC(pc) + case _ => + InterimResult.forUB( + InterpretationHandler.getEntity(state), + StringFlowFunction.ub, + Set(state.tacDependee), + continuation(state) + ) + } } - protected def processNewPC(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult + protected def processNew(implicit state: InterpretationState): ProperPropertyComputationResult } object InterpretationHandler { - def getEntityForDefSite(defSite: Int)(implicit state: DUSiteState): DUSiteEntity = - getEntityForPC(pcOfDefSite(defSite)(state.tac.stmts)) - - def getEntityForDefSite(defSite: Int, dm: DefinedMethod, tac: TAC, entity: SEntity): DUSiteEntity = - getEntityForPC(pcOfDefSite(defSite)(tac.stmts), dm, tac, entity) - - def getEntityForPC(pc: Int)(implicit state: DUSiteState): DUSiteEntity = - getEntityForPC(pc, state.dm, state.tac, state.entity) + def getEntity(implicit state: InterpretationState): MethodPC = getEntity(state.pc, state.dm) - def getEntity(state: DUSiteState): DUSiteEntity = - getEntityForPC(state.pc, state.dm, state.tac, state.entity) + def getEntity(pc: Int)(implicit state: InterpretationState): MethodPC = getEntity(pc, state.dm) - def getEntityForPC(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity): DUSiteEntity = - DUSiteEntity(pc, dm, tac, entity) + def getEntity(pc: Int, dm: DefinedMethod): MethodPC = MethodPC(pc, dm) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala index 329da42a8a..bdfd3d161a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala @@ -6,28 +6,29 @@ package analyses package string package interpretation -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.IdentityFlow /** * @author Maximilian Rüsch */ -object SimpleValueConstExprInterpreter extends StringInterpreter { +object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter { - override type T = SimpleValueConst + override type E = SimpleValueConst - def interpret(expr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val sci = expr match { - case ic: IntConst => StringConstancyInformationConst(StringTreeConst(ic.value.toString)) - case fc: FloatConst => StringConstancyInformationConst(StringTreeConst(fc.value.toString)) - case dc: DoubleConst => StringConstancyInformationConst(StringTreeConst(dc.value.toString)) - case lc: LongConst => StringConstancyInformationConst(StringTreeConst(lc.value.toString)) - case sc: StringConst => StringConstancyInformationConst(StringTreeConst(sc.value)) - case _ => StringConstancyInformation.neutralElement - } - - computeFinalResult(pc, sci) + override def interpretExpr(target: V, expr: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val pt = target.toPersistentForm(state.tac.stmts) + computeFinalResult(expr match { + case ic: IntConst => ConstantResultFlow.forVariable(pt, StringTreeConst(ic.value.toString)) + case fc: FloatConst => ConstantResultFlow.forVariable(pt, StringTreeConst(fc.value.toString)) + case dc: DoubleConst => ConstantResultFlow.forVariable(pt, StringTreeConst(dc.value.toString)) + case lc: LongConst => ConstantResultFlow.forVariable(pt, StringTreeConst(lc.value.toString)) + case sc: StringConst => ConstantResultFlow.forVariable(pt, StringTreeConst(sc.value)) + case _ => IdentityFlow + }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala deleted file mode 100644 index 8fd68596f7..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0ArrayAccessInterpreter.scala +++ /dev/null @@ -1,78 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string -package l0 -package interpretation - -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.collection.immutable.IntTrieSet -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler - -/** - * Responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] expressions without a call graph. - * - * @author Maximilian Rüsch - */ -case class L0ArrayAccessInterpreter(ps: PropertyStore) extends StringInterpreter { - - override type T = ArrayLoad[V] - - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val defSitePCs = getStoreAndLoadDefSitePCs(instr)(state.tac.stmts) - val results = defSitePCs.map { pc => ps(InterpretationHandler.getEntityForPC(pc), StringConstancyProperty.key) } - - if (results.exists(_.isRefinable)) { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - results.filter(_.isRefinable).toSet, - awaitAllFinalContinuation( - EPSDepender(instr, pc, state, results), - finalResult(pc) - ) - ) - } else { - finalResult(pc)(results.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) - } - } - - private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit - state: DUSiteState - ): ProperPropertyComputationResult = { - var resultSci = StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].sci - }) - if (resultSci.isTheNeutralElement) { - resultSci = StringConstancyInformation.lb - } - - computeFinalResult(pc, resultSci) - } - - private def getStoreAndLoadDefSitePCs(instr: T)(implicit stmts: Array[Stmt[V]]): List[Int] = { - var defSites = IntTrieSet.empty - instr.arrayRef.asVar.definedBy.toArray.filter(_ >= 0).sorted.foreach { next => - stmts(next).asAssignment.targetVar.usedBy.toArray.sorted.foreach { - stmts(_) match { - case ArrayStore(_, _, _, value) => - defSites = defSites ++ value.asVar.definedBy.toArray - case Assignment(_, _, expr: ArrayLoad[V]) => - defSites = defSites ++ expr.asArrayLoad.arrayRef.asVar.definedBy.toArray - case _ => - } - } - } - - val allDefSites = defSites ++ instr.arrayRef.asVar.definedBy.toArray.toIndexedSeq.filter(_ < 0) - allDefSites.toList.map(pcOfDefSite(_)).sorted - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index 9d7a56fa0e..67fec8671c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -8,8 +8,8 @@ package l0 package interpretation import org.opalj.br.Method -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP import org.opalj.fpcf.InterimResult @@ -19,23 +19,25 @@ import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** - * Processes [[NonVirtualFunctionCall]]s without a call graph. - * * @author Maximilian Rüsch */ trait L0FunctionCallInterpreter - extends StringInterpreter + extends AssignmentLikeBasedStringInterpreter with ParameterEvaluatingStringInterpreter { - override type T <: FunctionCall[V] + override type E <: FunctionCall[V] implicit val ps: PropertyStore protected[this] case class FunctionCallState( - state: DUSiteState, + state: InterpretationState, + target: PV, calleeMethods: Seq[Method], + parameters: Seq[PV], var tacDependees: Map[Method, EOptionP[Method, TACAI]], var returnDependees: Map[Method, Seq[EOptionP[SContext, StringConstancyProperty]]] = Map.empty ) { @@ -43,33 +45,6 @@ trait L0FunctionCallInterpreter var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) - private var _paramDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = Seq.empty - private var _paramEntityToPositionMapping: Map[DUSiteEntity, (Int, Int)] = Map.empty - - def paramDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]] = _paramDependees - - def updateParamDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { - val pos = _paramEntityToPositionMapping(newDependee.e) - _paramDependees = _paramDependees.updated( - pos._1, - _paramDependees(pos._1).updated( - pos._2, - newDependee - ) - ) - } - - def setParamDependees(newParamDependees: Seq[Seq[EOptionP[DUSiteEntity, StringConstancyProperty]]]): Unit = { - _paramDependees = newParamDependees - _paramEntityToPositionMapping = Map.empty - _paramDependees.zipWithIndex.map { - case (param, outerIndex) => param.zipWithIndex.map { - case (dependee, innerIndex) => - _paramEntityToPositionMapping += dependee.e -> (outerIndex, innerIndex) - } - } - } - def updateReturnDependee(method: Method, newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { returnDependees = returnDependees.updated( method, @@ -82,18 +57,16 @@ trait L0FunctionCallInterpreter def hasDependees: Boolean = { tacDependees.values.exists(_.isRefinable) || - returnDependees.values.flatten.exists(_.isRefinable) || - paramDependees.flatten.exists(_.isRefinable) + returnDependees.values.flatten.exists(_.isRefinable) } def dependees: Iterable[SomeEOptionP] = { tacDependees.values.filter(_.isRefinable) ++ - returnDependees.values.flatten.filter(_.isRefinable) ++ - paramDependees.flatten.filter(_.isRefinable) + returnDependees.values.flatten.filter(_.isRefinable) } } - protected def interpretArbitraryCallToMethods(implicit + protected def interpretArbitraryCallToFunctions(implicit callState: FunctionCallState ): ProperPropertyComputationResult = { callState.calleeMethods.foreach { m => @@ -128,27 +101,31 @@ trait L0FunctionCallInterpreter private def tryComputeFinalResult(implicit callState: FunctionCallState): ProperPropertyComputationResult = { if (callState.hasDependees) { InterimResult.forUB( - InterpretationHandler.getEntityForPC(callState.pc)(callState.state), - StringConstancyProperty.ub, + InterpretationHandler.getEntity(callState.state), + StringFlowFunction.ub, callState.dependees.toSet, continuation(callState) ) } else { - val parameterScis = callState.paramDependees.zipWithIndex.map { - case (params, index) => - (index, StringConstancyInformation.reduceMultiple(params.map(_.asFinal.p.sci))) - }.toMap - val methodScis = callState.calleeMethods.map { m => - if (callState.hasUnresolvableReturnValue(m)) { - StringConstancyInformation.lb - } else { - StringConstancyInformation.reduceMultiple(callState.returnDependees(m).map { - _.asFinal.p.sci.replaceParameters(parameterScis) - }) - } - } + val parameters = callState.parameters.zipWithIndex.map(x => (x._2, x._1)).toMap + + val flowFunction: StringFlowFunction = (env: StringTreeEnvironment) => + env.update( + callState.target, + StringTreeNode.reduceMultiple { + callState.calleeMethods.map { m => + if (callState.hasUnresolvableReturnValue(m)) { + StringTreeNode.lb + } else { + StringTreeNode.reduceMultiple(callState.returnDependees(m).map { + _.asFinal.p.sci.tree.replaceParameters(parameters.map { kv => (kv._1, env(kv._2)) }) + }) + } + } + } + ) - computeFinalResult(callState.pc, StringConstancyInformation.reduceMultiple(methodScis))(callState.state) + computeFinalResult(flowFunction)(callState.state) } } @@ -156,17 +133,13 @@ trait L0FunctionCallInterpreter eps match { case EUBP(m: Method, _: TACAI) => callState.tacDependees += m -> eps.asInstanceOf[EOptionP[Method, TACAI]] - interpretArbitraryCallToMethods(callState) + interpretArbitraryCallToFunctions(callState) - case EUBP(_: (_, _), _: StringConstancyProperty) => + case EUBP(_, _: StringConstancyProperty) => val contextEPS = eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]] callState.updateReturnDependee(contextEPS.e._2, contextEPS) tryComputeFinalResult(callState) - case EUBP(_: DUSiteEntity, _: StringConstancyProperty) => - callState.updateParamDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) - tryComputeFinalResult(callState) - case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 31ab797594..9d69bc33cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -8,12 +8,12 @@ package l0 package interpretation import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter +import org.opalj.tac.fpcf.properties.string.IdentityFlow /** * @inheritdoc @@ -22,51 +22,49 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInt */ class L0InterpretationHandler()( implicit - p: SomeProject, - ps: PropertyStore + p: SomeProject, + override val ps: PropertyStore ) extends InterpretationHandler { - override protected def processNewPC(pc: Int)(implicit - state: DUSiteState + override protected def processNew(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - val duSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); + val duSiteOpt = valueOriginOfPC(state.pc, state.tac.pcToIndex); if (duSiteOpt.isEmpty) { - throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") + throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: ${state.pc}") } state.tac.stmts(duSiteOpt.get) match { - case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) - - case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, expr: NewArray[V]) => L0NewArrayInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) + case Assignment(_, target, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(target, expr) + case Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(target, expr) // Currently unsupported - case Assignment(_, _, _: GetField[V]) => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.lb) + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.computeFinalLBFor(target) + + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(IdentityFlow) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(ps).interpret(expr, pc) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter(ps).interpret(expr, pc) + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) - case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, pc) - case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, pc) + case Assignment(_, target, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(target, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc, pc) - case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) - case _ => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) + case _ => StringInterpreter.computeFinalResult(IdentityFlow) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala deleted file mode 100644 index ac805ef9ef..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NewArrayInterpreter.scala +++ /dev/null @@ -1,74 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string -package l0 -package interpretation - -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler - -/** - * @author Maximilian Rüsch - */ -case class L0NewArrayInterpreter(ps: PropertyStore) extends StringInterpreter { - - override type T = NewArray[V] - - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - if (instr.counts.length != 1) { - // Only supports 1-D arrays - return computeFinalResult(pc, StringConstancyInformation.lb) - } - - // Get all sites that define array values and process them - val defSite = valueOriginOfPC(pc, state.tac.pcToIndex).get; - val arrValuesDefSites = state.tac.stmts(defSite).asAssignment.targetVar.asVar.usedBy.toArray.toList.sorted - val allResults = arrValuesDefSites.flatMap { ds => - if (ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[V]]) { - state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) - } - } else if (ds < 0) { - Seq(ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key)) - } else { - Seq.empty - } - } - - if (allResults.exists(_.isRefinable)) { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - allResults.filter(_.isRefinable).toSet, - awaitAllFinalContinuation( - EPSDepender(instr, pc, state, allResults), - finalResult(pc) - ) - ) - } else { - finalResult(pc)(allResults.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) - } - } - - private def finalResult(pc: Int)(results: Seq[SomeFinalEP])(implicit state: DUSiteState): Result = { - val resultsScis = results.map(_.p.asInstanceOf[StringConstancyProperty].sci) - val sci = if (resultsScis.forall(_.isTheNeutralElement)) { - // It might be that there are no results; in such a case, set the string information to the lower bound - StringConstancyInformation.lb - } else { - StringConstancyInformation.reduceMultiple(resultsScis) - } - - computeFinalResult(pc, sci) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 4e36e4b3bb..149624b694 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -8,7 +8,6 @@ package l0 package interpretation import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI @@ -21,21 +20,25 @@ import org.opalj.tac.fpcf.properties.TACAI case class L0NonVirtualFunctionCallInterpreter()( implicit val p: SomeProject, implicit val ps: PropertyStore -) extends StringInterpreter +) extends AssignmentLikeBasedStringInterpreter with L0FunctionCallInterpreter { - override type T = NonVirtualFunctionCall[V] + override type T = AssignmentLikeStmt[V] + override type E = NonVirtualFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) + override def interpretExpr(instr: T, expr: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val target = expr.receiver.asVar.toPersistentForm(state.tac.stmts) + val calleeMethod = expr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalResult(pc, StringConstancyInformation.lb) + return computeFinalLBFor(target) } val m = calleeMethod.value - val callState = FunctionCallState(state, Seq(m), Map((m, ps(m, TACAI.key)))) - callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) + val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + val callState = FunctionCallState(state, target, Seq(m), params, Map((m, ps(m, TACAI.key)))) - interpretArbitraryCallToMethods(callState) + interpretArbitraryCallToFunctions(callState) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 9d8ee837d8..a19c76ffe4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -7,60 +7,34 @@ package string package l0 package interpretation -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.fpcf.FinalEP -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.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch */ -case class L0NonVirtualMethodCallInterpreter(ps: PropertyStore) extends StringInterpreter { +object L0NonVirtualMethodCallInterpreter extends StringInterpreter { override type T = NonVirtualMethodCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { + override def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { instr.name match { - case "" => interpretInit(instr, pc) - case _ => computeFinalResult(pc, StringConstancyInformation.neutralElement) + case "" => interpretInit(instr) + case _ => computeFinalResult(IdentityFlow) } } - private def interpretInit(init: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { + private def interpretInit(init: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { init.params.size match { case 0 => - computeFinalResult(pc, StringConstancyInformation.neutralElement) + computeFinalResult(IdentityFlow) case _ => + val targetVar = init.receiver.asVar.toPersistentForm(state.tac.stmts) // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters - val results = init.params.head.asVar.definedBy.toList.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) - } - if (results.forall(_.isFinal)) { - finalResult(init.pc)(results.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]]) - } else { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - results.toSet, - awaitAllFinalContinuation( - EPSDepender(init, pc, state, results), - finalResult(pc) - ) - ) - } + val paramVar = init.params.head.asVar.toPersistentForm(state.tac.stmts) + + computeFinalResult((env: StringTreeEnvironment) => env.update(targetVar, env(paramVar))) } } - - private def finalResult(pc: Int)(results: Seq[SomeEPS])(implicit state: DUSiteState): Result = - computeFinalResult( - pc, - StringConstancyInformation.reduceMultiple(results.map { - _.asFinal.p.asInstanceOf[StringConstancyProperty].sci - }) - ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 496eb08c74..54cae19ad8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -9,17 +9,12 @@ package interpretation import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeConst -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeFinalEP -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch @@ -28,82 +23,69 @@ case class L0StaticFunctionCallInterpreter()( implicit override val p: SomeProject, override val ps: PropertyStore -) extends StringInterpreter +) extends AssignmentBasedStringInterpreter with L0ArbitraryStaticFunctionCallInterpreter with L0StringValueOfFunctionCallInterpreter { - override type T = StaticFunctionCall[V] + override type E = StaticFunctionCall[V] - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - instr.name match { - case "valueOf" if instr.declaringClass == ObjectType.String => processStringValueOf(instr, pc) - case _ => interpretArbitraryCall(instr, pc) + override def interpretExpr(target: V, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + call.name match { + case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) + case _ => interpretArbitraryCall(target, call) } } } private[string] trait L0ArbitraryStaticFunctionCallInterpreter - extends StringInterpreter + extends AssignmentBasedStringInterpreter with L0FunctionCallInterpreter { implicit val p: SomeProject - override type T = StaticFunctionCall[V] + override type E <: StaticFunctionCall[V] - def interpretArbitraryCall(instr: T, pc: Int)(implicit - state: DUSiteState + def interpretArbitraryCall(target: V, call: E)(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - val calleeMethod = instr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) + val calleeMethod = call.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalResult(pc, StringConstancyInformation.lb) + return computeFinalLBFor(target) } val m = calleeMethod.value - val callState = FunctionCallState(state, Seq(m), Map((m, ps(m, TACAI.key)))) - callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) + val pt = target.toPersistentForm(state.tac.stmts) + val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + val callState = FunctionCallState(state, pt, Seq(m), params, Map((m, ps(m, TACAI.key)))) - interpretArbitraryCallToMethods(callState) + interpretArbitraryCallToFunctions(callState) } } -private[string] trait L0StringValueOfFunctionCallInterpreter extends StringInterpreter { +private[string] trait L0StringValueOfFunctionCallInterpreter extends AssignmentBasedStringInterpreter { - override type T <: StaticFunctionCall[V] + override type E <: StaticFunctionCall[V] - val ps: PropertyStore + def processStringValueOf(target: V, call: E)(implicit state: InterpretationState): ProperPropertyComputationResult = { + val pt = target.toPersistentForm(state.tac.stmts) + val pp = call.params.head.asVar.toPersistentForm(state.tac.stmts) - def processStringValueOf(call: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - def finalResult(results: Seq[SomeFinalEP]): Result = { - // For char values, we need to do a conversion (as the returned results are integers) - val scis = results.map { r => r.p.asInstanceOf[StringConstancyProperty].sci } - val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { - scis.map { - case StringConstancyInformationConst(const: StringTreeConst) if const.isIntConst => - StringConstancyInformationConst(StringTreeConst(const.string.toInt.toChar.toString)) - case sci => - sci + val flowFunction: StringFlowFunction = if (call.descriptor.parameterType(0).toJava == "char") { + (env: StringTreeEnvironment) => + { + env(pp) match { + case const: StringTreeConst if const.isIntConst => + env.update(pt, StringTreeConst(const.string.toInt.toChar.toString)) + case tree => + env.update(pt, tree) + } } - } else { - scis - } - computeFinalResult(pc, StringConstancyInformation.reduceMultiple(finalScis)) - } - - val results = call.params.head.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) - } - if (results.exists(_.isRefinable)) { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - results.filter(_.isRefinable).toSet, - awaitAllFinalContinuation( - EPSDepender(call, call.pc, state, results), - finalResult - ) - ) } else { - finalResult(results.asInstanceOf[Seq[SomeFinalEP]]) + (env: StringTreeEnvironment) => env.update(pt, env(pp)) } + + computeFinalResult(flowFunction) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index f558839bfd..869367b994 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -12,30 +12,18 @@ import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.DoubleType import org.opalj.br.FloatType +import org.opalj.br.IntLikeType import org.opalj.br.ObjectType -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst -import org.opalj.br.fpcf.properties.string.StringConstancyInformationFunction import org.opalj.br.fpcf.properties.string.StringConstancyLevel -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeConcat import org.opalj.br.fpcf.properties.string.StringTreeConst -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EUBP -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimEP -import org.opalj.fpcf.InterimResult +import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat +import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEOptionP -import org.opalj.fpcf.SomeEPS -import org.opalj.fpcf.SomeFinalEP -import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue /** @@ -43,335 +31,164 @@ import org.opalj.value.TheIntegerValue * * @author Maximilian Rüsch */ -case class L0VirtualFunctionCallInterpreter( - override val ps: PropertyStore -) extends StringInterpreter +case class L0VirtualFunctionCallInterpreter() + extends AssignmentLikeBasedStringInterpreter with L0ArbitraryVirtualFunctionCallInterpreter with L0AppendCallInterpreter with L0SubstringCallInterpreter { - override type T = VirtualFunctionCall[V] + override type T = AssignmentLikeStmt[V] + override type E = 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 `toString` 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 [[interpretReplaceCall]]. - *
    • - *
    • - * Apart from these supported methods, a [[StringConstancyInformation.lb]] will be returned in case the passed - * method returns a [[java.lang.String]]. - *
    • - *
    - * - * If none of the above-described cases match, a [[NoResult]] will be returned. - */ - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - instr.name match { - case "append" => interpretAppendCall(instr, pc) - case "toString" => interpretToStringCall(instr, pc) - case "replace" => interpretReplaceCall(pc) - case "substring" if instr.descriptor.returnType == ObjectType.String => - interpretSubstringCall(instr, pc) + override def interpretExpr(instr: T, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val at = Option.unless(!instr.isAssignment)(instr.asAssignment.targetVar.asVar.toPersistentForm(state.tac.stmts)) + val pt = call.receiver.asVar.toPersistentForm(state.tac.stmts) + + call.name match { + case "append" => interpretAppendCall(at, pt, call) + case "toString" => interpretToStringCall(at, pt) + case "replace" => interpretReplaceCall(pt) + case "substring" if call.descriptor.returnType == ObjectType.String => + interpretSubstringCall(at, pt, call) case _ => - instr.descriptor.returnType match { + call.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => - interpretArbitraryCall(instr, pc) + if (at.isDefined) interpretArbitraryCall(at.get, call) + else computeFinalResult(IdentityFlow) + case _: IntLikeType => + computeFinalResult(ConstantResultFlow.forVariable(pt, StringTreeDynamicInt)) case FloatType | DoubleType => - computeFinalResult(pc, StringConstancyInformation.dynamicFloat) + computeFinalResult(ConstantResultFlow.forVariable(pt, StringTreeDynamicFloat)) case _ => - computeFinalResult(pc, StringConstancyInformation.neutralElement) + computeFinalResult(IdentityFlow) } } } - private def interpretToStringCall(call: T, pc: Int)(implicit - state: DUSiteState + private def interpretToStringCall(at: Option[PV], pt: PV)(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - def computeResult(eps: SomeEOptionP): ProperPropertyComputationResult = { - eps match { - case FinalP(sciP: StringConstancyProperty) => - computeFinalResult(pc, sciP.sci) - - case iep: InterimEP[_, _] if eps.pk == StringConstancyProperty.key => - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - iep.ub.asInstanceOf[StringConstancyProperty], - Set(eps), - computeResult - ) - - case _ if eps.pk == StringConstancyProperty.key => - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - Set(eps), - computeResult - ) - - case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") - } - } - - val persistentReceiverVar = call.receiver.asVar.toPersistentForm(state.tac.stmts) - if (persistentReceiverVar.equals(state.entity)) { - computeFinalResult(pc, StringConstancyInformationFunction(sci => sci)) + if (at.isDefined) { + computeFinalResult((env: StringTreeEnvironment) => env.update(at.get, env(pt))) } else { - computeResult(ps( - (persistentReceiverVar, state.dm.definedMethod), - StringConstancyProperty.key - )) + computeFinalResult(IdentityFlow) } } /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = - computeFinalResult(pc, StringConstancyInformationConst(StringTreeDynamicString)) + private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = + computeFinalResult(StringFlowFunction.lb(target)) } -private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends StringInterpreter { +private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { - protected def interpretArbitraryCall(call: T, pc: Int)(implicit - state: DUSiteState + protected def interpretArbitraryCall(target: PV, call: E)(implicit + state: InterpretationState ): ProperPropertyComputationResult = - computeFinalResult(pc, StringConstancyInformation.lb) + computeFinalResult(StringFlowFunction.lb(target)) } /** * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. */ -private[string] trait L0AppendCallInterpreter extends StringInterpreter { +private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { - override type T = VirtualFunctionCall[V] - - val ps: PropertyStore - - private[this] case class AppendCallState( - appendCall: T, - param: V, - defSitePC: Int, - state: DUSiteState, - var receiverDependeeOpt: Option[EOptionP[SContext, StringConstancyProperty]], - var valueDependees: Seq[EOptionP[DUSiteEntity, StringConstancyProperty]] - ) { - - def updateReceiverDependee(newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { - if (receiverDependeeOpt.isEmpty) { - throw new IllegalStateException("Encountered update of receiver when no dependee was defined!") - } - - receiverDependeeOpt = Some(newDependee) - } - - def updateValueDependee(newDependee: EOptionP[DUSiteEntity, StringConstancyProperty]): Unit = { - valueDependees = valueDependees.updated(valueDependees.indexWhere(_.e == newDependee.e), newDependee) - } + override type E = VirtualFunctionCall[V] - def hasDependees: Boolean = - (receiverDependeeOpt.isDefined && receiverDependeeOpt.get.isRefinable) || valueDependees.exists( - _.isRefinable - ) - - def dependees: Iterable[EOptionP[Entity, StringConstancyProperty]] = { - if (receiverDependeeOpt.isDefined && receiverDependeeOpt.get.isRefinable) { - valueDependees.filter(_.isRefinable) :+ receiverDependeeOpt.get - } else { - valueDependees.filter(_.isRefinable) - } - } - } - - def interpretAppendCall(appendCall: T, pc: Int)(implicit - state: DUSiteState + def interpretAppendCall(at: Option[PV], pt: PV, call: E)(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - val receiverVar = appendCall.receiver.asVar.toPersistentForm(state.tac.stmts) - val receiverResult = if (receiverVar.equals(state.entity)) { - // Previous state of the receiver will be handled by path resolution - None - } else { - // Get receiver results - Some(ps((receiverVar, state.dm.definedMethod), StringConstancyProperty.key)) - } - - // Get parameter results // .head because we want to evaluate only the first argument of append - val param = appendCall.params.head.asVar - val valueResults = param.definedBy.toList.sorted.map { ds => - val usedDS = if (ds >= 0 && state.tac.stmts(ds).isAssignment && state.tac.stmts(ds).asAssignment.expr.isNew) { - state.tac.stmts(ds).asAssignment.targetVar.usedBy.toArray.min - } else { - ds - } - ps(InterpretationHandler.getEntityForDefSite(usedDS), StringConstancyProperty.key) - } - implicit val appendState: AppendCallState = - AppendCallState(appendCall, param, pc, state, receiverResult, valueResults) - - tryComputeFinalAppendCallResult - } - - private def continuation(appendState: AppendCallState)(eps: SomeEPS): ProperPropertyComputationResult = { - eps match { - case EUBP(_: DUSiteEntity, _: StringConstancyProperty) => - appendState.updateValueDependee(eps.asInstanceOf[EOptionP[DUSiteEntity, StringConstancyProperty]]) - tryComputeFinalAppendCallResult(appendState) - - case UBP(_: StringConstancyProperty) => - appendState.updateReceiverDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) - tryComputeFinalAppendCallResult(appendState) - - case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") - } - } + val paramVar = call.params.head.asVar.toPersistentForm(state.tac.stmts) - private def tryComputeFinalAppendCallResult(implicit - appendState: AppendCallState - ): ProperPropertyComputationResult = { - if (appendState.hasDependees) { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(appendState.defSitePC)(appendState.state), - StringConstancyProperty.ub, - appendState.dependees.toSet, - continuation(appendState) - ) - } else { - val valueSci = transformAppendValueResult( - appendState.valueDependees.asInstanceOf[Seq[FinalEP[DUSiteEntity, StringConstancyProperty]]] - ) + computeFinalResult((env: StringTreeEnvironment) => { + val valueState = env(paramVar) - val resultSci = if (appendState.receiverDependeeOpt.isDefined) { - val receiverSci = appendState.receiverDependeeOpt.get.asFinal.p.sci - StringConstancyInformationConst(StringTreeConcat.fromNodes(receiverSci.tree, valueSci.tree)) - } else { - StringConstancyInformationFunction(pv => StringTreeConcat.fromNodes(pv, valueSci.tree)) + val transformedValueState = paramVar.value.computationalType match { + case ComputationalTypeInt => + if (call.descriptor.parameterType(0).isCharType && valueState.isInstanceOf[StringTreeConst]) { + StringTreeConst(valueState.asInstanceOf[StringTreeConst].string.toInt.toChar.toString) + } else { + valueState + } + case ComputationalTypeFloat | ComputationalTypeDouble => + if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { + valueState + } else { + StringTreeDynamicFloat + } + case _ => + valueState } - computeFinalResult(appendState.defSitePC, resultSci)(appendState.state) - } - } - - private def transformAppendValueResult( - results: Seq[FinalEP[DUSiteEntity, StringConstancyProperty]] - )(implicit appendState: AppendCallState): StringConstancyInformation = { - val sciValues = results.map(_.p.sci) - val newValueSci = StringConstancyInformation.reduceMultiple(sciValues) - - appendState.param.value.computationalType match { - case ComputationalTypeInt => - if (appendState.appendCall.descriptor.parameterType(0).isCharType && - newValueSci.constancyLevel == StringConstancyLevel.CONSTANT && - sciValues.exists(!_.isTheNeutralElement) - ) { - val charSciValues = sciValues map { - case StringConstancyInformationConst(const: StringTreeConst) if const.isIntConst => - StringConstancyInformationConst(StringTreeConst(const.string.toInt.toChar.toString)) - case sci => - sci - } - StringConstancyInformation.reduceMultiple(charSciValues) - } else { - newValueSci - } - case ComputationalTypeFloat | ComputationalTypeDouble => - if (newValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { - newValueSci - } else { - StringConstancyInformation.dynamicFloat - } - case _ => - newValueSci - } + var newEnv = env + if (at.isDefined) { + newEnv = newEnv.update(at.get, transformedValueState) + } + newEnv.update(pt, transformedValueState) + }) } } /** * Interprets calls to [[String#substring]]. */ -private[string] trait L0SubstringCallInterpreter extends StringInterpreter { - - override type T = VirtualFunctionCall[V] +private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStringInterpreter { - val ps: PropertyStore + override type E <: VirtualFunctionCall[V] - def interpretSubstringCall(substringCall: T, pc: Int)(implicit - state: DUSiteState + def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - val receiverResults = substringCall.receiver.asVar.definedBy.toList.sorted.map { ds => - ps(InterpretationHandler.getEntityForDefSite(ds), StringConstancyProperty.key) + if (at.isEmpty) { + return computeFinalResult(IdentityFlow); } - if (receiverResults.exists(_.isRefinable)) { - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - receiverResults.toSet, - awaitAllFinalContinuation( - EPSDepender(substringCall, substringCall.pc, state, receiverResults), - computeFinalSubstringCallResult(substringCall, pc) - ) - ) - } else { - computeFinalSubstringCallResult(substringCall, pc)(receiverResults.asInstanceOf[Seq[SomeFinalEP]]) - } - } - - private def computeFinalSubstringCallResult(substringCall: T, pc: Int)( - results: Seq[SomeFinalEP] - )(implicit state: DUSiteState): Result = { - val receiverSci = StringConstancyInformation.reduceMultiple(results.map { - _.p.asInstanceOf[StringConstancyProperty].sci - }) - if (!receiverSci.tree.isInstanceOf[StringTreeConst]) { - // We cannot yet interpret substrings of mixed values - computeFinalResult(pc, StringConstancyInformation.lb) - } else { - val parameterCount = substringCall.params.size - parameterCount match { - case 1 => - substringCall.params.head.asVar.value match { - case intValue: TheIntegerValue => - computeFinalResult( - pc, - StringConstancyInformationConst( - StringTreeConst( - receiverSci.tree.asInstanceOf[StringTreeConst].string.substring(intValue.value) - ) - ) - ) - case _ => - computeFinalResult(pc, StringConstancyInformation.lb) - } + val parameterCount = call.params.size + parameterCount match { + case 1 => + call.params.head.asVar.value match { + case intValue: TheIntegerValue => + computeFinalResult((env: StringTreeEnvironment) => { + env(pt) match { + case const: StringTreeConst => + env.update(at.get, StringTreeConst(const.string.substring(intValue.value))) + case _ => + env.update(at.get, StringTreeNode.lb) + } + }) + case _ => + computeFinalResult(StringFlowFunction.noFlow(at.get)) + } - case 2 => - (substringCall.params.head.asVar.value, substringCall.params(1).asVar.value) match { - case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => - computeFinalResult( - pc, - StringConstancyInformationConst( - StringTreeConst( - receiverSci.tree.asInstanceOf[StringTreeConst].string.substring( + case 2 => + (call.params.head.asVar.value, call.params(1).asVar.value) match { + case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => + computeFinalResult((env: StringTreeEnvironment) => { + env(pt) match { + case const: StringTreeConst => + env.update( + at.get, + StringTreeConst(const.string.substring( firstIntValue.value, secondIntValue.value - ) + )) ) - ) - ) - case _ => - computeFinalResult(pc, StringConstancyInformation.lb) - } + case _ => + env.update(at.get, StringTreeNode.lb) + } + }) + case _ => + computeFinalResult(StringFlowFunction.noFlow(at.get)) + } - case _ => throw new IllegalStateException( - s"Unexpected parameter count for ${substringCall.descriptor.toJava}. Expected one or two, got $parameterCount" - ) - } + case _ => throw new IllegalStateException( + s"Unexpected parameter count for ${call.descriptor.toJava}. Expected one or two, got $parameterCount" + ) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 49c34c624b..98a7e309ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -7,8 +7,8 @@ package string package l0 package interpretation -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.properties.string.IdentityFlow /** * @author Maximilian Rüsch @@ -18,24 +18,14 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { 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, a [[StringConstancyInformation.neutralElement]] will be returned. + * Currently, this function supports no method calls. However, it treats [[StringBuilder.setLength]] such that it + * will return the lower bound for now. */ - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - val sci = instr.name match { + override def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { + instr.name match { // IMPROVE interpret argument for setLength - case "setLength" => StringConstancyInformation.neutralElement - case _ => StringConstancyInformation.neutralElement + case "setLength" => computeFinalLBFor(instr.receiver.asVar) + case _ => computeFinalResult(IdentityFlow) } - computeFinalResult(pc, sci) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index a69d874872..506be61be1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -12,9 +12,9 @@ import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyInformationConst import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeNull import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.EOptionP @@ -29,6 +29,8 @@ import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis +import org.opalj.tac.fpcf.properties.string.ConstantResultFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunction /** * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields @@ -42,9 +44,9 @@ case class L1FieldReadInterpreter( project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider -) extends StringInterpreter { +) extends AssignmentBasedStringInterpreter { - override type T = FieldRead[V] + override type E = FieldRead[V] /** * To analyze a read operation of field, ''f'', all write accesses, ''wa_f'', to ''f'' have to be analyzed. @@ -72,8 +74,8 @@ case class L1FieldReadInterpreter( } private case class FieldReadState( - defSitePC: Int, - state: DUSiteState, + var target: PV, + var hasWriteInSameMethod: Boolean = false, var hasInit: Boolean = false, var hasUnresolvableAccess: Boolean = false, var accessDependees: Seq[EOptionP[SContext, StringConstancyProperty]] = Seq.empty @@ -98,32 +100,38 @@ case class L1FieldReadInterpreter( * approximated using all write accesses as well as with the lower bound and "null" => in these cases fields are * [[org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC]]. */ - override def interpret(instr: T, pc: Int)(implicit state: DUSiteState): ProperPropertyComputationResult = { - // 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 analysis could then use that analysis. - if (!StringAnalysis.isSupportedType(instr.declaredFieldType)) { - return computeFinalResult(pc, StringConstancyInformation.lb) + override def interpretExpr(target: V, fieldRead: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + if (!StringAnalysis.isSupportedType(fieldRead.declaredFieldType)) { + return computeFinalLBFor(target) } - val definedField = declaredFields(instr.declaringClass, instr.name, instr.declaredFieldType).asDefinedField + val definedField = + declaredFields(fieldRead.declaringClass, fieldRead.name, fieldRead.declaredFieldType).asDefinedField val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq if (writeAccesses.length > fieldWriteThreshold) { - return computeFinalResult(pc, StringConstancyInformation.lb) + return computeFinalLBFor(target) } + val persistentTarget = target.toPersistentForm(state.tac.stmts) if (writeAccesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value - return computeFinalResult( - pc, - StringConstancyInformationConst(StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString)) - ) + return computeFinalResult(ConstantResultFlow.forVariable( + persistentTarget, + StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) + )) } - implicit val accessState: FieldReadState = FieldReadState(pc, state) + implicit val accessState: FieldReadState = FieldReadState(persistentTarget) writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod + if (method == state.dm.definedMethod) { + accessState.hasWriteInSameMethod = true + } + if (method.name == "" || method.name == "") { accessState.hasInit = true } @@ -140,36 +148,46 @@ case class L1FieldReadInterpreter( tryComputeFinalResult } - private def tryComputeFinalResult(implicit accessState: FieldReadState): ProperPropertyComputationResult = { - if (accessState.hasDependees) { + private def tryComputeFinalResult(implicit + accessState: FieldReadState, + state: InterpretationState + ): ProperPropertyComputationResult = { + if (accessState.hasWriteInSameMethod) { + // We cannot handle writes to a field that is read in the same method at the moment as the flow functions do + // not capture field state. This can be improved upon in the future. + computeFinalLBFor(accessState.target) + } else if (accessState.hasDependees) { InterimResult.forUB( - InterpretationHandler.getEntityForPC(accessState.defSitePC)(accessState.state), - StringConstancyProperty.ub, + InterpretationHandler.getEntity, + StringFlowFunction.ub, accessState.dependees.toSet, - continuation(accessState) + continuation(accessState, state) ) } else { - var scis = accessState.accessDependees.map(_.asFinal.p.sci) + var trees = accessState.accessDependees.map(_.asFinal.p.sci.tree) // 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 (!accessState.hasInit) { - scis = scis :+ StringConstancyInformation.nullElement + trees = trees :+ StringTreeNull } // If an access could not be resolved, append a dynamic element if (accessState.hasUnresolvableAccess) { - scis = scis :+ StringConstancyInformation.lb + trees = trees :+ StringTreeNode.lb } - computeFinalResult(accessState.defSitePC, StringConstancyInformation.reduceMultiple(scis))(accessState.state) + computeFinalResult(ConstantResultFlow.forVariable(accessState.target, StringTreeNode.reduceMultiple(trees))) } } - private def continuation(accessState: FieldReadState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation( + accessState: FieldReadState, + state: InterpretationState + )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) - tryComputeFinalResult(accessState) + tryComputeFinalResult(accessState, state) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 7b0aa41ca4..1936d0a2f8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -15,18 +15,16 @@ import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0ArrayAccessInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NewArrayInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualMethodCallInterpreter +import org.opalj.tac.fpcf.properties.string.IdentityFlow /** * @inheritdoc @@ -42,58 +40,56 @@ class L1InterpretationHandler( val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processNewPC(pc: Int)(implicit - state: DUSiteState + override protected def processNew(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - val defSiteOpt = valueOriginOfPC(pc, state.tac.pcToIndex); + val defSiteOpt = valueOriginOfPC(state.pc, state.tac.pcToIndex); if (defSiteOpt.isEmpty) { - throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: $pc") + throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: ${state.pc}") } state.tac.stmts(defSiteOpt.get) match { - case Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpret(expr, pc) + case Assignment(_, target, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(target, expr) - case Assignment(_, _, expr: ArrayLoad[V]) => L0ArrayAccessInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, expr: NewArray[V]) => L0NewArrayInterpreter(ps).interpret(expr, pc) - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) + // Currently unsupported + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, target, _: GetField[V]) => StringInterpreter.computeFinalLBFor(target) - case Assignment(_, _, expr: FieldRead[V]) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( - expr, - pc - ) - case ExprStmt(_, expr: FieldRead[V]) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpret( - expr, - pc + case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(IdentityFlow) + + case Assignment(_, targetVar, expr: FieldRead[V]) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( + targetVar, + expr ) + // Field reads without result usage are irrelevant + case ExprStmt(_, _: FieldRead[V]) => StringInterpreter.computeFinalResult(IdentityFlow) - case Assignment(_, _, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpret(expr, pc) - case ExprStmt(_, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpret(expr, pc) + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) - case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpret(expr, pc) + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, pc) - case ExprStmt(_, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpret(expr, pc) + case Assignment(_, target, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(target, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpret(expr, pc) + case Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(target, expr) case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter.interpret(vmc, pc) + L0VirtualMethodCallInterpreter.interpret(vmc) case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter(ps).interpret(nvmc, pc) + L0NonVirtualMethodCallInterpreter.interpret(nvmc) - case _ => - StringInterpreter.computeFinalResult(pc, StringConstancyInformation.neutralElement) + case _ => StringInterpreter.computeFinalResult(IdentityFlow) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index baf22d3e17..8edc7b9aaf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -14,7 +14,6 @@ import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP @@ -37,61 +36,44 @@ import org.opalj.tac.fpcf.properties.TACAI class L1VirtualFunctionCallInterpreter( override implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider -) extends L0VirtualFunctionCallInterpreter(ps) +) extends L0VirtualFunctionCallInterpreter with StringInterpreter with L0FunctionCallInterpreter { - override type T = VirtualFunctionCall[V] + override type E = VirtualFunctionCall[V] private case class CalleeDepender( - pc: Int, + target: PV, methodContext: Context, var calleeDependee: EOptionP[DefinedMethod, Callees] ) - override protected def interpretArbitraryCall(instr: T, pc: Int)( - implicit state: DUSiteState + override protected def interpretArbitraryCall(target: PV, call: E)( + implicit state: InterpretationState ): ProperPropertyComputationResult = { - val depender = CalleeDepender(pc, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) + val depender = CalleeDepender(target, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) - depender.calleeDependee match { - case FinalP(c: Callees) => - val methods = getMethodsFromCallees(depender.pc, depender.methodContext, c) - if (methods.isEmpty) { - computeFinalResult(pc, StringConstancyInformation.lb) - } else { - val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val callState = FunctionCallState(state, tacDependees.keys.toSeq, tacDependees) - callState.setParamDependees(evaluateParameters(getParametersForPC(pc))) - - interpretArbitraryCallToMethods(callState) - } - - case _ => - InterimResult.forUB( - InterpretationHandler.getEntityForPC(pc), - StringConstancyProperty.ub, - Set(depender.calleeDependee), - continuation(state, depender) - ) - } + continuation(state, depender)(depender.calleeDependee.asInstanceOf[SomeEPS]) } private def continuation( - state: DUSiteState, + state: InterpretationState, depender: CalleeDepender )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(c: Callees) => - val methods = getMethodsFromCallees(state.pc, depender.methodContext, c) + implicit val _state: InterpretationState = state + + val methods = getMethodsFromCallees(depender.methodContext, c) if (methods.isEmpty) { - computeFinalResult(depender.pc, StringConstancyInformation.lb)(state) + computeFinalLBFor(depender.target) } else { val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val callState = FunctionCallState(state, tacDependees.keys.toSeq, tacDependees) - callState.setParamDependees(evaluateParameters(getParametersForPC(depender.pc)(state))(state)) + val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + val callState = + FunctionCallState(state, depender.target, tacDependees.keys.toSeq, params, tacDependees) - interpretArbitraryCallToMethods(callState) + interpretArbitraryCallToFunctions(callState) } case UBP(_: Callees) => @@ -107,10 +89,12 @@ class L1VirtualFunctionCallInterpreter( } } - private def getMethodsFromCallees(pc: Int, context: Context, callees: Callees): Seq[Method] = { + private def getMethodsFromCallees(context: Context, callees: Callees)(implicit + state: InterpretationState + ): Seq[Method] = { val methods = ListBuffer[Method]() // IMPROVE only process newest callees - callees.callees(context, pc).map(_.method).foreach { + callees.callees(context, state.pc).map(_.method).foreach { case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) case _ => // IMPROVE add some uncertainty element if methods with unknown body exist } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala index 0e8ae42429..936b2ae861 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala @@ -31,4 +31,6 @@ package object string { * should be given as a [[org.opalj.br.PC]]. */ case class DUSiteEntity(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) + + case class MethodPC(pc: Int, dm: DefinedMethod) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala new file mode 100644 index 0000000000..f9639549e4 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -0,0 +1,54 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package properties +package string + +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey +import org.opalj.fpcf.PropertyMetaInformation + +/** + * @author Maximilian Rüsch + */ +sealed trait StringFlowFunctionPropertyMetaInformation extends PropertyMetaInformation { + + final type Self = StringFlowFunction +} + +trait StringFlowFunction extends (StringTreeEnvironment => StringTreeEnvironment) + with Property + with StringFlowFunctionPropertyMetaInformation { + + final def key: PropertyKey[StringFlowFunction] = StringFlowFunction.key +} + +object StringFlowFunction extends StringFlowFunctionPropertyMetaInformation { + + private final val propertyName = "opalj.StringFlowFunction" + + override val key: PropertyKey[StringFlowFunction] = PropertyKey.create(propertyName) + + def ub: StringFlowFunction = ConstantResultFlow.forAll(StringTreeNeutralElement) + def ub(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeNeutralElement) + def lb(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeDynamicString) + def noFlow(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeInvalidElement) +} + +object ConstantResultFlow { + def forAll(result: StringTreeNode): StringFlowFunction = + (env: StringTreeEnvironment) => env.updateAll(result) + + def forVariable(v: PV, result: StringTreeNode): StringFlowFunction = + (env: StringTreeEnvironment) => env.update(v, result) +} + +object IdentityFlow extends StringFlowFunction { + + override def apply(env: StringTreeEnvironment): StringTreeEnvironment = env +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala new file mode 100644 index 0000000000..0725a1c079 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -0,0 +1,30 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package properties +package string + +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNode + +/** + * @author Maximilian Rüsch + */ +case class StringTreeEnvironment(private val map: Map[PV, StringTreeNode]) { + + def apply(v: PV): StringTreeNode = map(v) + + def update(v: PV, value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.updated(v, value)) + + def updateAll(value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.map { kv => (kv._1, value) }) + + def join(other: StringTreeEnvironment): StringTreeEnvironment = { + this // TODO implement join here + } +} + +object StringTreeEnvironment { + def lb: StringTreeEnvironment = + StringTreeEnvironment(Map.empty[PV, StringTreeNode].withDefaultValue(StringTreeDynamicString)) +} From c64f8ec2b9549b3efbd468838730c745b4424261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 17 May 2024 23:50:33 +0200 Subject: [PATCH 420/583] Correct package file name --- .../fpcf/analyses/string/{string_analysis.scala => package.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{string_analysis.scala => package.scala} (100%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala similarity index 100% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/string_analysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala From bd39888e28ee7ba96f7e581b8628529b1eacaf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 May 2024 00:37:12 +0200 Subject: [PATCH 421/583] Remove unused functions --- .../string/StringConstancyInformation.scala | 31 ++----------------- .../L1VirtualFunctionCallInterpreter.scala | 4 +-- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala index d0ef451d9d..71bb98f303 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala @@ -8,40 +8,15 @@ package string /** * @author Maximilian Rüsch */ -trait StringConstancyInformation { - - def treeFn: StringTreeNode => StringTreeNode - def tree: StringTreeNode +case class StringConstancyInformation(tree: StringTreeNode) { final def isTheNeutralElement: Boolean = tree.isNeutralElement final def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel final def toRegex: String = tree.toRegex } -case class StringConstancyInformationConst(override val tree: StringTreeNode) extends StringConstancyInformation { - - override def treeFn: StringTreeNode => StringTreeNode = _ => tree -} - -case class StringConstancyInformationFunction(override val treeFn: StringTreeNode => StringTreeNode) - extends StringConstancyInformation { - - override def tree: StringTreeNode = treeFn(StringTreeNeutralElement) -} - object StringConstancyInformation { - def lb: StringConstancyInformation = StringConstancyInformationConst(StringTreeDynamicString) - def ub: StringConstancyInformation = StringConstancyInformationConst(StringTreeNeutralElement) - - def nullElement: StringConstancyInformation = StringConstancyInformationConst(StringTreeNull) - - def getElementForParameterPC(paramPC: Int): StringConstancyInformation = { - if (paramPC >= -1) { - throw new IllegalArgumentException(s"Invalid parameter pc given: $paramPC") - } - // Parameters start at PC -2 downwards - val paramPosition = Math.abs(paramPC + 2) - StringConstancyInformationConst(StringTreeParameter(paramPosition)) - } + def lb: StringConstancyInformation = StringConstancyInformation(StringTreeDynamicString) + def ub: StringConstancyInformation = StringConstancyInformation(StringTreeNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 8edc7b9aaf..f8a297885d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -14,7 +14,6 @@ import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult @@ -26,6 +25,7 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunction /** * Processes [[VirtualFunctionCall]]s similar to the [[L0VirtualFunctionCallInterpreter]] but handles arbitrary calls @@ -80,7 +80,7 @@ class L1VirtualFunctionCallInterpreter( depender.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] InterimResult.forUB( InterpretationHandler.getEntity(state), - StringConstancyProperty.ub, + StringFlowFunction.ub, Set(depender.calleeDependee), continuation(state, depender) ) From 68855e3dbb363754c794acf0e0d7e7257641cfa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 May 2024 11:35:56 +0200 Subject: [PATCH 422/583] Compute super flow graph in structural analysis as well --- .../flowanalysis/StructuralAnalysis.scala | 29 +++++++++++++++---- .../string/flowanalysis/package.scala | 1 + 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 917bacd7fd..d6e02f056b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -10,7 +10,9 @@ import scala.collection.mutable import org.opalj.graphs.DominatorTree +import scalax.collection.OneOrMore import scalax.collection.edges.DiEdge +import scalax.collection.hyperedges.DiHyperEdge import scalax.collection.immutable.Graph /** @@ -18,8 +20,9 @@ import scalax.collection.immutable.Graph */ object StructuralAnalysis { - def analyze(graph: FlowGraph, entry: Region): (FlowGraph, ControlTree) = { + def analyze(graph: FlowGraph, entry: Region): (FlowGraph, SuperFlowGraph, ControlTree) = { var g = graph + var sg = graph.asInstanceOf[SuperFlowGraph] var curEntry = entry var controlTree = Graph.empty[Region, DiEdge[Region]] @@ -29,12 +32,19 @@ object StructuralAnalysis { var postCtr = 1 val post = mutable.ListBuffer.empty[Region] - def replace(currentGraph: FlowGraph, subRegions: Set[Region], regionType: RegionType): (FlowGraph, Region) = { + def replace( + currentGraph: FlowGraph, + currentSuperGraph: SuperFlowGraph, + subRegions: Set[Region], + regionType: RegionType + ): (FlowGraph, SuperFlowGraph, Region) = { val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) var newGraph: FlowGraph = currentGraph + var newSuperGraph: SuperFlowGraph = currentSuperGraph // Compact newGraph = newGraph.incl(newRegion) + newSuperGraph = newSuperGraph.incl(newRegion) val maxPost = post.indexOf(subRegions.maxBy(post.indexOf)) post(maxPost) = newRegion // Removing old regions from the graph is done later @@ -50,13 +60,18 @@ object StructuralAnalysis { if (!subRegions.contains(source) && subRegions.contains(target)) { newGraph += DiEdge(source, newRegion) + newSuperGraph += DiEdge(source, newRegion) + newSuperGraph -= DiEdge(source, target) } else if (subRegions.contains(source) && !subRegions.contains(target)) { newGraph += DiEdge(newRegion, target) + newSuperGraph += DiEdge(newRegion, target) + newSuperGraph -= DiEdge(source, target) } } newGraph = newGraph.removedAll(subRegions, Set.empty) + newSuperGraph = newSuperGraph.incl(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subRegions).get)) - (newGraph, newRegion) + (newGraph, newSuperGraph, newRegion) } PostOrderTraversal.foreachInTraversalFrom[Region, FlowGraph](g, curEntry)(post.append) { (x, y) => @@ -71,8 +86,9 @@ object StructuralAnalysis { if (acyclicRegionOpt.isDefined) { val (arType, nodes) = acyclicRegionOpt.get - val (newGraph, newRegion) = replace(g, nodes, arType) + val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, arType) g = newGraph + sg = newSuperGraph for { node <- nodes } { @@ -109,8 +125,9 @@ object StructuralAnalysis { if (cyclicRegionOpt.isDefined) { val (crType, nodes) = cyclicRegionOpt.get - val (newGraph, newRegion) = replace(g, nodes, crType) + val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, crType) g = newGraph + sg = newSuperGraph for { node <- nodes } { @@ -129,7 +146,7 @@ object StructuralAnalysis { outerIterations += 1 } - (g, controlTree) + (g, sg, controlTree) } private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexedNodes: Seq[A], domTree: DominatorTree)( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index bdd4a76e29..f67386ba27 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -32,6 +32,7 @@ package object flowanalysis { type ControlTree = Graph[Region, DiEdge[Region]] type FlowGraph = Graph[Region, DiEdge[Region]] + type SuperFlowGraph = Graph[Region, Edge[Region]] object FlowGraph extends TypedGraphFactory[Region, DiEdge[Region]] { From a00b64c449964fa64070e9b841cf7fc6d3bd2bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 May 2024 12:05:39 +0200 Subject: [PATCH 423/583] Generalize used flow graph nodes to a trait --- .../{Region.scala => FlowGraphNode.scala} | 12 ++++- .../flowanalysis/StructuralAnalysis.scala | 32 ++++++------- .../string/flowanalysis/package.scala | 48 ++++++++++--------- 3 files changed, 53 insertions(+), 39 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/{Region.scala => FlowGraphNode.scala} (71%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala similarity index 71% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index 93f5141be1..31e02724fd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/Region.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -21,7 +21,17 @@ case object WhileLoop extends CyclicRegionType case object NaturalLoop extends CyclicRegionType case object Improper extends CyclicRegionType -case class Region(regionType: RegionType, nodeIds: Set[Int]) { +sealed trait FlowGraphNode { + def nodeIds: Set[Int] +} + +case class Region(regionType: RegionType, override val nodeIds: Set[Int]) extends FlowGraphNode { override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")})" } + +case class Statement(nodeId: Int) extends FlowGraphNode { + override val nodeIds: Set[Int] = Set(nodeId) + + override def toString: String = s"Statement($nodeId)" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index d6e02f056b..2ef78b5293 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -20,61 +20,61 @@ import scalax.collection.immutable.Graph */ object StructuralAnalysis { - def analyze(graph: FlowGraph, entry: Region): (FlowGraph, SuperFlowGraph, ControlTree) = { + def analyze(graph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { var g = graph var sg = graph.asInstanceOf[SuperFlowGraph] var curEntry = entry - var controlTree = Graph.empty[Region, DiEdge[Region]] + var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] var outerIterations = 0 while (g.order > 1 && outerIterations < 100) { // Find post order depth first traversal order for nodes var postCtr = 1 - val post = mutable.ListBuffer.empty[Region] + val post = mutable.ListBuffer.empty[FlowGraphNode] def replace( currentGraph: FlowGraph, currentSuperGraph: SuperFlowGraph, - subRegions: Set[Region], + subNodes: Set[FlowGraphNode], regionType: RegionType ): (FlowGraph, SuperFlowGraph, Region) = { - val newRegion = Region(regionType, subRegions.flatMap(_.nodeIds)) + val newRegion = Region(regionType, subNodes.flatMap(_.nodeIds)) var newGraph: FlowGraph = currentGraph var newSuperGraph: SuperFlowGraph = currentSuperGraph // Compact newGraph = newGraph.incl(newRegion) newSuperGraph = newSuperGraph.incl(newRegion) - val maxPost = post.indexOf(subRegions.maxBy(post.indexOf)) + val maxPost = post.indexOf(subNodes.maxBy(post.indexOf)) post(maxPost) = newRegion // Removing old regions from the graph is done later - post.filterInPlace(r => !subRegions.contains(r)) + post.filterInPlace(r => !subNodes.contains(r)) postCtr = post.indexOf(newRegion) // Replace edges for { e <- newGraph.edges } { - val source: Region = e.outer.source - val target: Region = e.outer.target + val source: FlowGraphNode = e.outer.source + val target: FlowGraphNode = e.outer.target - if (!subRegions.contains(source) && subRegions.contains(target)) { + if (!subNodes.contains(source) && subNodes.contains(target)) { newGraph += DiEdge(source, newRegion) newSuperGraph += DiEdge(source, newRegion) newSuperGraph -= DiEdge(source, target) - } else if (subRegions.contains(source) && !subRegions.contains(target)) { + } else if (subNodes.contains(source) && !subNodes.contains(target)) { newGraph += DiEdge(newRegion, target) newSuperGraph += DiEdge(newRegion, target) newSuperGraph -= DiEdge(source, target) } } - newGraph = newGraph.removedAll(subRegions, Set.empty) - newSuperGraph = newSuperGraph.incl(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subRegions).get)) + newGraph = newGraph.removedAll(subNodes, Set.empty) + newSuperGraph = newSuperGraph.incl(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get)) (newGraph, newSuperGraph, newRegion) } - PostOrderTraversal.foreachInTraversalFrom[Region, FlowGraph](g, curEntry)(post.append) { (x, y) => + PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry)(post.append) { (x, y) => x.nodeIds.head.compare(y.nodeIds.head) } @@ -104,10 +104,10 @@ object StructuralAnalysis { indexedNodes.indexOf(curEntry), g.get(curEntry).diPredecessors.nonEmpty, index => { f => - g.get(indexedNodes(index)).diSuccessors.foreach(ds => f(indexedNodes.indexOf(ds))) + g.get(indexedNodes(index)).diSuccessors.foreach(ds => f(indexedNodes.indexOf(ds.outer))) }, index => { f => - g.get(indexedNodes(index)).diPredecessors.foreach(ds => f(indexedNodes.indexOf(ds))) + g.get(indexedNodes(index)).diPredecessors.foreach(ds => f(indexedNodes.indexOf(ds.outer))) }, indexedNodes.size - 1 ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index f67386ba27..e23c79935c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -30,42 +30,42 @@ import scalax.collection.io.dot.NodeId */ package object flowanalysis { - type ControlTree = Graph[Region, DiEdge[Region]] - type FlowGraph = Graph[Region, DiEdge[Region]] - type SuperFlowGraph = Graph[Region, Edge[Region]] + type ControlTree = Graph[FlowGraphNode, DiEdge[FlowGraphNode]] + type FlowGraph = Graph[FlowGraphNode, DiEdge[FlowGraphNode]] + type SuperFlowGraph = Graph[FlowGraphNode, Edge[FlowGraphNode]] - object FlowGraph extends TypedGraphFactory[Region, DiEdge[Region]] { + object FlowGraph extends TypedGraphFactory[FlowGraphNode, DiEdge[FlowGraphNode]] { def apply[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): FlowGraph = { val edges = cfg.allNodes.flatMap { case bb: BasicBlock => - val firstNode = Region(Block, Set(bb.startPC)) - var currentEdges = Seq.empty[DiEdge[Region]] + val firstNode = Statement(bb.startPC) + var currentEdges = Seq.empty[DiEdge[FlowGraphNode]] if (bb.startPC != bb.endPC) { Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrPC => currentEdges :+= DiEdge( currentEdges.lastOption.map(_.target).getOrElse(firstNode), - Region(Block, Set(instrPC)) + Statement(instrPC) ) } } val lastNode = if (currentEdges.nonEmpty) currentEdges.last.target else firstNode - currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Region(Block, Set(s.nodeId)))) + currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Statement(s.nodeId))) case n => - n.successors.map(s => DiEdge(Region(Block, Set(n.nodeId)), Region(Block, Set(s.nodeId)))) + n.successors.map(s => DiEdge(Statement(n.nodeId), Statement(s.nodeId))) }.toSet val g = Graph.from(edges) - val normalReturnNode = Region(Block, Set(cfg.normalReturnNode.nodeId)) - val abnormalReturnNode = Region(Block, Set(cfg.abnormalReturnNode.nodeId)) + val normalReturnNode = Statement(cfg.normalReturnNode.nodeId) + val abnormalReturnNode = Statement(cfg.abnormalReturnNode.nodeId) val hasNormalReturn = cfg.normalReturnNode.predecessors.nonEmpty val hasAbnormalReturn = cfg.abnormalReturnNode.predecessors.nonEmpty (hasNormalReturn, hasAbnormalReturn) match { case (true, true) => - val allReturnNode = Region(Block, Set(-42)) + val allReturnNode = Statement(-42) g.incl(DiEdge(normalReturnNode, allReturnNode)).incl(DiEdge(abnormalReturnNode, allReturnNode)) case (true, false) => @@ -81,9 +81,9 @@ package object flowanalysis { } } - def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Region = Region(Block, Set(cfg.startBlock.nodeId)) + def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = Statement(cfg.startBlock.nodeId) - def toDot[N <: Region, E <: Edge[N]](graph: Graph[N, E]): String = { + def toDot[N <: FlowGraphNode, E <: Edge[N]](graph: Graph[N, E]): String = { val root = DotRootGraph( directed = true, id = Some(Id("MyDot")), @@ -123,9 +123,10 @@ package object flowanalysis { DotAttr(Id("style"), Id("filled")), DotAttr( Id("fillcolor"), - node.regionType match { - case _: AcyclicRegionType => Id(""""green"""") - case _: CyclicRegionType => Id(""""purple"""") + node match { + case Region(_: AcyclicRegionType, _) => Id(""""green"""") + case Region(_: CyclicRegionType, _) => Id(""""purple"""") + case _ => Id(""""white"""") } ) ) @@ -146,14 +147,17 @@ package object flowanalysis { ) } - def enrichWithControlTree(flowGraph: FlowGraph, controlTree: ControlTree): Graph[Region, Edge[Region]] = { + def enrichWithControlTree( + flowGraph: FlowGraph, + controlTree: ControlTree + ): Graph[FlowGraphNode, Edge[FlowGraphNode]] = { var combinedGraph = flowGraph - .++(controlTree.nodes.map(_.outer), Iterable.empty) - .asInstanceOf[Graph[Region, Edge[Region]]] + .++[FlowGraphNode, DiEdge[FlowGraphNode]](controlTree.nodes.map(_.outer), Iterable.empty) + .asInstanceOf[Graph[FlowGraphNode, Edge[FlowGraphNode]]] for { - node <- controlTree.nodes.toOuter.asInstanceOf[Set[Region]] - nodes = combinedGraph.nodes.filter((n: Graph[Region, Edge[Region]]#NodeT) => + node <- controlTree.nodes.toOuter + nodes = combinedGraph.nodes.filter((n: Graph[FlowGraphNode, Edge[FlowGraphNode]]#NodeT) => n.outer.nodeIds.subsetOf(node.nodeIds) ).map(_.outer) actualSubsetNodes = nodes.filter(n => n.nodeIds != node.nodeIds) From 9f4bd78fc41c9f66f8ad592e08f6c8e26aac931e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 May 2024 12:10:11 +0200 Subject: [PATCH 424/583] Add a special global exit node --- .../fpcf/analyses/string/flowanalysis/FlowGraphNode.scala | 6 ++++++ .../tac/fpcf/analyses/string/flowanalysis/package.scala | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index 31e02724fd..567ca985b9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -35,3 +35,9 @@ case class Statement(nodeId: Int) extends FlowGraphNode { override def toString: String = s"Statement($nodeId)" } + +object GlobalExit extends FlowGraphNode { + override val nodeIds: Set[Int] = Set(Int.MinValue) + + override def toString: String = s"GlobalExit" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index e23c79935c..38fbeaeca5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -65,8 +65,7 @@ package object flowanalysis { (hasNormalReturn, hasAbnormalReturn) match { case (true, true) => - val allReturnNode = Statement(-42) - g.incl(DiEdge(normalReturnNode, allReturnNode)).incl(DiEdge(abnormalReturnNode, allReturnNode)) + g.incl(DiEdge(normalReturnNode, GlobalExit)).incl(DiEdge(abnormalReturnNode, GlobalExit)) case (true, false) => g.excl(abnormalReturnNode) From 636423eb06800b3a51e3777f7a322c3227818248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 18 May 2024 12:46:24 +0200 Subject: [PATCH 425/583] Enable computing the entry node to a region during structural analysis --- .../string/flowanalysis/FlowGraphNode.scala | 5 ++- .../flowanalysis/StructuralAnalysis.scala | 37 +++++++++++++------ .../string/flowanalysis/package.scala | 6 +-- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index 567ca985b9..a46b2ced6c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -25,9 +25,10 @@ sealed trait FlowGraphNode { def nodeIds: Set[Int] } -case class Region(regionType: RegionType, override val nodeIds: Set[Int]) extends FlowGraphNode { +case class Region(regionType: RegionType, override val nodeIds: Set[Int], entry: FlowGraphNode) extends FlowGraphNode { - override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")})" + override def toString: String = + s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")}; ${entry.toString})" } case class Statement(nodeId: Int) extends FlowGraphNode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 2ef78b5293..23d499dd36 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -36,9 +36,10 @@ object StructuralAnalysis { currentGraph: FlowGraph, currentSuperGraph: SuperFlowGraph, subNodes: Set[FlowGraphNode], + entry: FlowGraphNode, regionType: RegionType ): (FlowGraph, SuperFlowGraph, Region) = { - val newRegion = Region(regionType, subNodes.flatMap(_.nodeIds)) + val newRegion = Region(regionType, subNodes.flatMap(_.nodeIds), entry) var newGraph: FlowGraph = currentGraph var newSuperGraph: SuperFlowGraph = currentSuperGraph @@ -84,9 +85,9 @@ object StructuralAnalysis { val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, n) n = newStartingNode if (acyclicRegionOpt.isDefined) { - val (arType, nodes) = acyclicRegionOpt.get + val (arType, nodes, entry) = acyclicRegionOpt.get - val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, arType) + val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, arType) g = newGraph sg = newSuperGraph for { @@ -123,9 +124,9 @@ object StructuralAnalysis { val cyclicRegionOpt = locateCyclicRegion(g, n, reachUnder) if (cyclicRegionOpt.isDefined) { - val (crType, nodes) = cyclicRegionOpt.get + val (crType, nodes, entry) = cyclicRegionOpt.get - val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, crType) + val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, crType) g = newGraph sg = newSuperGraph for { @@ -170,8 +171,9 @@ object StructuralAnalysis { private def locateAcyclicRegion[A, G <: Graph[A, DiEdge[A]]]( graph: G, startingNode: A - ): (A, Option[(AcyclicRegionType, Set[A])]) = { + ): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] + var entry: graph.NodeT = graph.get(startingNode) // Expand nSet down var n = graph.get(startingNode) @@ -187,10 +189,12 @@ object StructuralAnalysis { n = graph.get(startingNode) while (n.diPredecessors.size == 1 && (n.outer == startingNode || n.diSuccessors.size == 1)) { nSet += n + entry = n n = n.diPredecessors.head } if (n.diSuccessors.size == 1) { nSet += n + entry = n } def locateProperAcyclicInterval: Option[AcyclicRegionType] = { @@ -208,6 +212,7 @@ object StructuralAnalysis { None } else { nSet = currentNodeSet ++ currentSuccessors + entry = n Some(Proper) } @@ -230,6 +235,7 @@ object StructuralAnalysis { && k.diPredecessors.size == 1 ) { nSet = Set(n, m, k) + entry = n Some(IfThenElse) } else if (( m.diSuccessors.size == 1 @@ -244,6 +250,7 @@ object StructuralAnalysis { ) ) { nSet = Set(n, m, k) + entry = n Some(IfThen) } else { locateProperAcyclicInterval @@ -254,16 +261,17 @@ object StructuralAnalysis { None } - (n.outer, rType.map((_, nSet.map(_.outer)))) + (n.outer, rType.map((_, nSet.map(_.outer), entry))) } private def locateCyclicRegion[A, G <: Graph[A, DiEdge[A]]]( graph: G, startingNode: A, reachUnder: Set[A] - ): Option[(CyclicRegionType, Set[A])] = { + ): Option[(CyclicRegionType, Set[A], A)] = { if (reachUnder.size == 1) { - return if (graph.find(DiEdge(startingNode, startingNode)).isDefined) Some((SelfLoop, reachUnder)) + return if (graph.find(DiEdge(startingNode, startingNode)).isDefined) + Some((SelfLoop, reachUnder, reachUnder.head)) else None } @@ -277,9 +285,16 @@ object StructuralAnalysis { && graph.get(m).diPredecessors.size == 1 && graph.get(m).diSuccessors.size == 1 ) { - Some((WhileLoop, reachUnder)) + Some((WhileLoop, reachUnder, startingNode)) } else { - Some((NaturalLoop, reachUnder)) + val enteringNodes = + reachUnder.filter(graph.get(_).diPredecessors.exists(dp => !reachUnder.contains(dp.outer))) + + if (enteringNodes.size > 1) { + throw new IllegalStateException("Found more than one entering node for a natural loop!") + } + + Some((NaturalLoop, reachUnder, enteringNodes.head)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index 38fbeaeca5..f8d21a3a9d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -123,9 +123,9 @@ package object flowanalysis { DotAttr( Id("fillcolor"), node match { - case Region(_: AcyclicRegionType, _) => Id(""""green"""") - case Region(_: CyclicRegionType, _) => Id(""""purple"""") - case _ => Id(""""white"""") + case Region(_: AcyclicRegionType, _, _) => Id(""""green"""") + case Region(_: CyclicRegionType, _, _) => Id(""""purple"""") + case _ => Id(""""white"""") } ) ) From 2f52fa69a10b35c9ad3cb2e7ff60f8f34c412958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 20 May 2024 20:01:52 +0200 Subject: [PATCH 426/583] Implement rudimentary data flow analysis --- .../string_analysis/l0/L0TestMethods.java | 13 + .../org/opalj/fpcf/StringAnalysisTest.scala | 19 +- .../analyses/string/ComputationState.scala | 55 +++-- .../fpcf/analyses/string/StringAnalysis.scala | 226 +++++++----------- .../analyses/string/StringInterpreter.scala | 22 +- .../flowanalysis/DataFlowAnalysis.scala | 66 +++++ .../string/flowanalysis/package.scala | 20 +- .../BinaryExprInterpreter.scala | 7 +- .../InterpretationHandler.scala | 10 +- .../SimpleValueConstExprInterpreter.scala | 13 +- .../analyses/string/l0/L0StringAnalysis.scala | 8 +- .../L0InterpretationHandler.scala | 23 +- .../L0NonVirtualMethodCallInterpreter.scala | 2 +- .../L0StaticFunctionCallInterpreter.scala | 18 +- .../L0VirtualFunctionCallInterpreter.scala | 4 +- .../analyses/string/l1/L1StringAnalysis.scala | 14 +- .../L1FieldReadInterpreter.scala | 10 +- .../L1InterpretationHandler.scala | 23 +- .../analyses/string/preprocessing/Path.scala | 29 --- .../string/preprocessing/PathFinder.scala | 61 ----- .../string/StringFlowFunction.scala | 2 +- .../string/StringTreeEnvironment.scala | 12 +- 22 files changed, 321 insertions(+), 336 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/Path.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 7559183ecc..f97579bc6f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -109,6 +109,19 @@ public void simpleStringConcat() { analyzeString(className2); } + @StringDefinitionsCollection( + value = "checks if a string value with append(s) is determined correctly", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") + } + ) + public void simpleStringConcat2() { + String className1 = "java.lang."; + System.out.println(className1); + className1 += "String"; + analyzeString(className1); + } + @StringDefinitionsCollection( value = "checks if the substring of a constant string value is determined correctly", stringDefinitions = { 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 10ec19474f..3ef94e6a6d 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 @@ -22,7 +22,9 @@ import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.SEntity import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringFlowAnalysis sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -156,21 +158,22 @@ class L0StringAnalysisTest extends StringAnalysisTest { } describe("the org.opalj.fpcf.L0StringAnalysis is started") { - val as = executeAnalyses(LazyL0StringAnalysis) + val as = executeAnalyses(LazyL0StringAnalysis, LazyL0StringFlowAnalysis) val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities + .filter(entity => entity._2.name.startsWith("simpleStringConcat2")) // Currently broken L0 Tests .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) - // it("can be executed without exceptions") { - newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + it("can be executed without exceptions") { + newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() + as.propertyStore.waitOnPhaseCompletion() + as.propertyStore.shutdown() - validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) - // } + validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) + } } } @@ -194,7 +197,7 @@ class L1StringAnalysisTest extends StringAnalysisTest { } describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses(LazyL1StringAnalysis) + val as = executeAnalyses(LazyL1StringAnalysis, LazyL1StringFlowAnalysis) val entities = determineEntitiesToAnalyze(as.project) // L0 Tests diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 9084f91705..65aac6a203 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -6,46 +6,61 @@ package fpcf package analyses package string +import scala.collection.mutable + import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.Property -import org.opalj.tac.fpcf.analyses.string.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree +import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph +import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * 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(dm: DefinedMethod, entity: SContext) { +case class ComputationState(dm: DefinedMethod, entity: SContext, var tacDependee: EOptionP[Method, TACAI]) { + + def tac: TAC = { + if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) + tacDependee.ub.tac.get + else + throw new IllegalStateException("Cannot get a TAC from a TACAI with no or empty upper bound!") + } - /** - * The Three-Address Code of the entity's method - */ - var tac: TAC = _ + var startEnv: StringTreeEnvironment = _ + var flowGraph: FlowGraph = _ + var superFlowGraph: SuperFlowGraph = _ + var controlTree: ControlTree = _ - /** - * The computed lean path that corresponds to the given entity - */ - var computedLeanPaths: Seq[Path] = _ + private val pcToDependeeMapping: mutable.Map[Int, EOptionP[MethodPC, StringFlowFunction]] = mutable.Map.empty - var tacDependee: Option[EOptionP[Method, TACAI]] = _ + def updateDependee(pc: Int, dependee: EOptionP[MethodPC, StringFlowFunction]): Unit = + pcToDependeeMapping.update(pc, dependee) - /** - * If not empty, this routine can only produce an intermediate result - */ - var dependees: List[EOptionP[DUSiteEntity, Property]] = List() -} + def dependees: Set[EOptionP[MethodPC, StringFlowFunction]] = pcToDependeeMapping.values.filter(_.isRefinable).toSet -case class DUSiteState(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) + def hasDependees: Boolean = pcToDependeeMapping.valuesIterator.exists(_.isRefinable) + + def getFlowFunctionsByPC: Map[Int, StringFlowFunction] = pcToDependeeMapping.map { kv => + ( + kv._1, + if (kv._2.hasUBP) kv._2.ub + else StringFlowFunction.ub + ) + }.toMap +} -private[string] case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { +case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { def tac: TAC = { if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) tacDependee.ub.tac.get else - throw new IllegalStateException("Cannot get a tac from a TACAI with no or empty upper bound!") + throw new IllegalStateException("Cannot get a TAC from a TACAI with no or empty upper bound!") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index a8e694ed6c..01d5d70bc7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -5,11 +5,9 @@ package fpcf package analyses package string -import org.opalj.ai.FormalParametersOriginOffset import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.FieldType import org.opalj.br.Method -import org.opalj.br.ObjectType import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys @@ -17,26 +15,29 @@ 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.properties.string.StringConstancyInformationConst +import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.br.fpcf.properties.string.StringTreeParameter +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimEUB import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph +import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement +import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.preprocessing.Path -import org.opalj.tac.fpcf.analyses.string.preprocessing.PathElement -import org.opalj.tac.fpcf.analyses.string.preprocessing.PathFinder import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch @@ -46,17 +47,20 @@ trait StringAnalysis extends FPCFAnalysis { val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(data: SContext): ProperPropertyComputationResult = { - val state = ComputationState(declaredMethods(data._2), data) - - val tacaiEOptP = ps(data._2, TACAI.key) - if (tacaiEOptP.isRefinable) { - state.tacDependee = Some(tacaiEOptP) - getInterimResult(state) - } else if (tacaiEOptP.ub.tac.isEmpty) { + val state = ComputationState(declaredMethods(data._2), data, ps(data._2, TACAI.key)) + + if (state.tacDependee.isRefinable) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees.toSet, + continuation(state) + ) + } else if (state.tacDependee.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body Result(state.entity, StringConstancyProperty.lb) } else { - state.tac = tacaiEOptP.ub.tac.get determinePossibleStrings(state) } } @@ -69,116 +73,58 @@ trait StringAnalysis extends FPCFAnalysis { private def determinePossibleStrings(implicit state: ComputationState): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac - val uVar = state.entity._1.toValueOriginForm(tac.pcToIndex) - val defSites = uVar.definedBy.toArray.sorted - - // TODO put a function parameter with their parameter string tree into the flow analysis - // Interpret a function / method parameter using the parameter information in state - if (defSites.head < 0) { - // TODO what do we do with string builder parameters? - if (pc <= FormalParametersOriginOffset) { + state.startEnv = StringTreeEnvironment(Map.empty.withDefault { pv: PV => + val defPCs = pv.defPCs.toList.sorted + if (defPCs.head >= 0) { + StringTreeNeutralElement + } else { + val pc = defPCs.head if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - return Result(FinalEP(InterpretationHandler.getEntity(state), sff)) - - return StringInterpreter.computeFinalLBFor(state.entity._1) + StringTreeDynamicString } else { - return StringInterpreter.computeFinalResult(ConstantResultFlow.forVariable( - state.entity._1, - StringTreeParameter.forParameterPC(pc) - )) + StringTreeParameter.forParameterPC(pc) } } + }) - val ep = ps( - InterpretationHandler.getEntityForDefSite(defSites.head, state.dm, tac, state.entity._1), - StringConstancyProperty.key - ) - if (ep.isRefinable) { - state.dependees = ep :: state.dependees - return InterimResult.forUB( - state.entity, - StringConstancyProperty.ub, - state.dependees.toSet, - continuation(state) - ) - } else { - return Result(state.entity, ep.asFinal.p) - } - } - - if (state.computedLeanPaths == null) { - state.computedLeanPaths = computeLeanPaths(uVar) - } + state.flowGraph = FlowGraph(tac.cfg) + val (_, superFlowGraph, controlTree) = + StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) + state.superFlowGraph = superFlowGraph + state.controlTree = controlTree - if (state.computedLeanPaths.isEmpty) { - return Result(state.entity, StringConstancyProperty.lb) - } + state.flowGraph.nodes.toOuter.foreach { + case Statement(pc) if pc >= 0 => + state.updateDependee(pc, propertyStore(MethodPC(pc, state.dm), StringFlowFunction.key)) - state.computedLeanPaths.flatMap(_.elements.map(_.pc)).distinct.foreach { pc => - propertyStore( - InterpretationHandler.getEntityForPC(pc, state.dm, tac, state.entity._1), - StringConstancyProperty.key - ) match { - case FinalEP(e, _) => - state.dependees = state.dependees.filter(_.e != e) - case ep => - state.dependees = ep :: state.dependees - } + case _ => } - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } + computeResults } - /** - * 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. - * @return Returns a final result if (already) available. Otherwise, an intermediate result will - * be returned. - */ - protected[this] def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case FinalP(tac: TACAI) if - eps.pk.equals(TACAI.key) && - state.tacDependee.isDefined && - state.tacDependee.get == eps => - state.tac = tac.tac.get - state.tacDependee = Some(eps.asInstanceOf[FinalEP[Method, TACAI]]) + case FinalP(_: TACAI) if eps.pk.equals(TACAI.key) => + state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] determinePossibleStrings(state) - case FinalEP(e: DUSiteEntity, _) if eps.pk.equals(StringConstancyProperty.key) => - state.dependees = state.dependees.filter(_.e != e) + case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunction.key) => + state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunction]]) + computeResults(state) - // No more dependees => Return the result for this analysis run - if (state.dependees.isEmpty) { - computeFinalResult(state) - } else { - getInterimResult(state) - } case _ => getInterimResult(state) } } - /** - * 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 state The final computation state. For this state the following criteria must apply: - * For each [[PathElement]], 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(state: ComputationState): Result = Result(state.entity, computeNewUpperBound(state)) + private def computeResults(implicit state: ComputationState): ProperPropertyComputationResult = { + if (state.hasDependees) { + getInterimResult(state) + } else { + Result(state.entity, computeNewUpperBound(state)) + } + } private def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { InterimResult( @@ -191,29 +137,13 @@ trait StringAnalysis extends FPCFAnalysis { } private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { - if (state.computedLeanPaths != null) { - val reducedStringTree = if (state.computedLeanPaths.isEmpty) { - StringTreeNeutralElement - } else { - StringTreeOr(state.computedLeanPaths.map { PathFinder.transformPath(_, state.tac)(state, ps) }) - } - - StringConstancyProperty(StringConstancyInformationConst(reducedStringTree.simplify)) - } else { - StringConstancyProperty.lb - } - } + val resultEnv = DataFlowAnalysis.compute( + state.controlTree, + state.superFlowGraph, + state.getFlowFunctionsByPC + )(state.startEnv) - private def computeLeanPaths(value: V)(implicit tac: TAC): Seq[Path] = { - if (value.value.isReferenceValue && ( - value.value.asReferenceValue.asReferenceType.mostPreciseObjectType == ObjectType.StringBuilder - || value.value.asReferenceValue.asReferenceType.mostPreciseObjectType == ObjectType.StringBuffer - ) - ) { - PathFinder.findPath(value, tac).map(Seq(_)).getOrElse(Seq.empty) - } else { - value.definedBy.toList.sorted.map(ds => Path(List(PathElement(ds)(tac.stmts)))) - } + StringConstancyProperty(StringConstancyInformation(resultEnv(state.entity._1))) } } @@ -266,11 +196,10 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), - PropertyBounds.ub(StringFlowFunction), - PropertyBounds.lub(StringConstancyProperty) + PropertyBounds.ub(StringFlowFunction) ) - override final type InitializationData = (StringAnalysis, InterpretationHandler) + override final type InitializationData = StringAnalysis override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -283,11 +212,38 @@ trait LazyStringAnalysis extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - // TODO double register lazy computation for pc scoped entities as well - ps.registerLazyPropertyComputation(StringConstancyProperty.key, initData._1.analyze) - ps.registerLazyPropertyComputation(StringFlowFunction.key, initData._2.analyze) + ps.registerLazyPropertyComputation(StringConstancyProperty.key, initData.analyze) + + initData + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} + +sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunction) + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) + + override final type InitializationData = InterpretationHandler + + 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 = {} +} + +trait LazyStringFlowAnalysis + extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(StringFlowFunction.key, initData.analyze) - initData._1 + initData } override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 210e11f48f..428ec3c44d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -45,6 +45,21 @@ object StringInterpreter { def computeFinalResult(sff: StringFlowFunction)(implicit state: InterpretationState): Result = Result(FinalEP(InterpretationHandler.getEntity(state), sff)) + + def findUVarForDVar(dVar: V)(implicit state: InterpretationState): V = { + state.tac.stmts(dVar.usedBy.head) match { + case Assignment(_, _, expr: Var[V]) => expr.asVar + case ExprStmt(_, expr: Var[V]) => expr.asVar + + case Assignment(_, _, call: InstanceFunctionCall[V]) => call.receiver.asVar + case ExprStmt(_, call: InstanceFunctionCall[V]) => call.receiver.asVar + + case ExprStmt(_, call: InstanceMethodCall[V]) => call.receiver.asVar + + case _ => + throw new IllegalArgumentException(s"Cannot determine uVar from $dVar") + } + } } trait ParameterEvaluatingStringInterpreter extends StringInterpreter { @@ -76,8 +91,9 @@ trait AssignmentBasedStringInterpreter extends AssignmentLikeBasedStringInterpre override final def interpretExpr(instr: T, expr: E)(implicit state: InterpretationState - ): ProperPropertyComputationResult = - interpretExpr(instr.targetVar, expr) + ): ProperPropertyComputationResult = { + interpretExpr(StringInterpreter.findUVarForDVar(instr.targetVar).toPersistentForm(state.tac.stmts), expr) + } - def interpretExpr(target: V, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult + def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala new file mode 100644 index 0000000000..f4d24733c9 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package flowanalysis + +import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment + +object DataFlowAnalysis { + + def compute( + controlTree: ControlTree, + superFlowGraph: SuperFlowGraph, + flowFunctionByPc: Map[Int, StringFlowFunction] + )(startEnv: StringTreeEnvironment): StringTreeEnvironment = { + val startNodeCandidates = controlTree.nodes.filter(_.diPredecessors.isEmpty) + if (startNodeCandidates.size != 1) { + throw new IllegalStateException("Found more than one start node in the control tree!") + } + + val startNode = startNodeCandidates.head.outer + pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc)(startNode, startEnv) + } + + private def pipeThroughNode( + controlTree: ControlTree, + superFlowGraph: SuperFlowGraph, + flowFunctionByPc: Map[Int, StringFlowFunction] + )( + node: FlowGraphNode, + env: StringTreeEnvironment + ): StringTreeEnvironment = { + val pipe = pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc) _ + val childNodes = controlTree.get(node).diSuccessors.map(_.outer) + val limitedFlowGraph = superFlowGraph.filter(n => childNodes.contains(n.outer)) + + node match { + case Statement(pc) if pc >= 0 => flowFunctionByPc(pc)(env) + case Statement(_) => env + + case Region(Block, _, entry) => + var currentEnv = pipe(entry, env) + var currentNode = limitedFlowGraph.get(entry) + while (currentNode.diSuccessors.nonEmpty) { + currentEnv = pipe(currentNode.outer, currentEnv) + currentNode = currentNode.diSuccessors.head + } + + currentEnv + + case Region(IfThenElse, _, entry) => + val entryNode = limitedFlowGraph.get(entry) + val branches = (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) + + val envAfterEntry = pipe(entry, env) + val envAfterBranches = (pipe(branches._1, envAfterEntry), pipe(branches._2, envAfterEntry)) + + envAfterBranches._1.join(envAfterBranches._2) + + case _ => env + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index f8d21a3a9d..c158ecd2b7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -36,25 +36,32 @@ package object flowanalysis { object FlowGraph extends TypedGraphFactory[FlowGraphNode, DiEdge[FlowGraphNode]] { + private def mapInstrIndexToPC[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]])(index: Int): Int = { + if (index >= 0) cfg.code.instructions(index).pc + else index + } + def apply[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): FlowGraph = { + val toPC = mapInstrIndexToPC(cfg) _ + val edges = cfg.allNodes.flatMap { case bb: BasicBlock => - val firstNode = Statement(bb.startPC) + val firstNode = Statement(toPC(bb.startPC)) var currentEdges = Seq.empty[DiEdge[FlowGraphNode]] if (bb.startPC != bb.endPC) { - Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrPC => + Range.inclusive(bb.startPC, bb.endPC).tail.foreach { instrIndex => currentEdges :+= DiEdge( currentEdges.lastOption.map(_.target).getOrElse(firstNode), - Statement(instrPC) + Statement(toPC(instrIndex)) ) } } val lastNode = if (currentEdges.nonEmpty) currentEdges.last.target else firstNode - currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Statement(s.nodeId))) + currentEdges ++ bb.successors.map(s => DiEdge(lastNode, Statement(toPC(s.nodeId)))) case n => - n.successors.map(s => DiEdge(Statement(n.nodeId), Statement(s.nodeId))) + n.successors.map(s => DiEdge(Statement(toPC(n.nodeId)), Statement(toPC(s.nodeId)))) }.toSet val g = Graph.from(edges) @@ -80,7 +87,8 @@ package object flowanalysis { } } - def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = Statement(cfg.startBlock.nodeId) + def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = + Statement(mapInstrIndexToPC(cfg)(cfg.startBlock.nodeId)) def toDot[N <: FlowGraphNode, E <: Edge[N]](graph: Graph[N, E]): String = { val root = DotRootGraph( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala index e461082ab8..d34b52aeff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala @@ -29,13 +29,12 @@ object BinaryExprInterpreter extends AssignmentBasedStringInterpreter { * * For all other expressions, [[IdentityFlow]] will be returned. */ - override def interpretExpr(target: V, expr: E)(implicit + override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - val pt = target.toPersistentForm(state.tac.stmts) computeFinalResult(expr.cTpe match { - case ComputationalTypeInt => ConstantResultFlow.forVariable(pt, StringTreeDynamicInt) - case ComputationalTypeFloat => ConstantResultFlow.forVariable(pt, StringTreeDynamicFloat) + case ComputationalTypeInt => ConstantResultFlow.forVariable(target, StringTreeDynamicInt) + case ComputationalTypeFloat => ConstantResultFlow.forVariable(target, StringTreeDynamicFloat) case _ => IdentityFlow }) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 60234a2ca8..b77bcd77f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -8,11 +8,11 @@ package interpretation import org.opalj.br.DefinedMethod import org.opalj.br.Method +import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.ConstantResultFlow @@ -27,9 +27,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunction * * @author Maximilian Rüsch */ -abstract class InterpretationHandler { - - def ps: PropertyStore +abstract class InterpretationHandler extends FPCFAnalysis { def analyze(entity: MethodPC): ProperPropertyComputationResult = { val tacaiEOptP = ps(entity.dm.definedMethod, TACAI.key) @@ -52,9 +50,9 @@ abstract class InterpretationHandler { private def continuation(state: InterpretationState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case finalEP: FinalEP[Method, TACAI] if + case finalEP: FinalEP[_, _] if eps.pk.equals(TACAI.key) => - state.tacDependee = finalEP + state.tacDependee = finalEP.asInstanceOf[FinalEP[Method, TACAI]] processNew(state) case _ => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala index bdfd3d161a..8f09ab64c3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala @@ -18,16 +18,15 @@ object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter override type E = SimpleValueConst - override def interpretExpr(target: V, expr: E)(implicit + override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - val pt = target.toPersistentForm(state.tac.stmts) computeFinalResult(expr match { - case ic: IntConst => ConstantResultFlow.forVariable(pt, StringTreeConst(ic.value.toString)) - case fc: FloatConst => ConstantResultFlow.forVariable(pt, StringTreeConst(fc.value.toString)) - case dc: DoubleConst => ConstantResultFlow.forVariable(pt, StringTreeConst(dc.value.toString)) - case lc: LongConst => ConstantResultFlow.forVariable(pt, StringTreeConst(lc.value.toString)) - case sc: StringConst => ConstantResultFlow.forVariable(pt, StringTreeConst(sc.value)) + case ic: IntConst => ConstantResultFlow.forVariable(target, StringTreeConst(ic.value.toString)) + case fc: FloatConst => ConstantResultFlow.forVariable(target, StringTreeConst(fc.value.toString)) + case dc: DoubleConst => ConstantResultFlow.forVariable(target, StringTreeConst(dc.value.toString)) + case lc: LongConst => ConstantResultFlow.forVariable(target, StringTreeConst(lc.value.toString)) + case sc: StringConst => ConstantResultFlow.forVariable(target, StringTreeConst(sc.value)) case _ => IdentityFlow }) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala index 84d36370c9..ae94eb218c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala @@ -17,6 +17,10 @@ class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis object LazyL0StringAnalysis extends LazyStringAnalysis { - override def init(p: SomeProject, ps: PropertyStore): InitializationData = - (new L0StringAnalysis(p), L0InterpretationHandler()(p, ps)) + override def init(p: SomeProject, ps: PropertyStore): InitializationData = new L0StringAnalysis(p) +} + +object LazyL0StringFlowAnalysis extends LazyStringFlowAnalysis { + + override def init(p: SomeProject, ps: PropertyStore): InitializationData = L0InterpretationHandler(p) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 9d69bc33cd..7e19516131 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -9,7 +9,6 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter @@ -20,11 +19,7 @@ import org.opalj.tac.fpcf.properties.string.IdentityFlow * * @author Maximilian Rüsch */ -class L0InterpretationHandler()( - implicit - p: SomeProject, - override val ps: PropertyStore -) extends InterpretationHandler { +class L0InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { override protected def processNew(implicit state: InterpretationState @@ -35,9 +30,9 @@ class L0InterpretationHandler()( } state.tac.stmts(duSiteOpt.get) match { - case Assignment(_, target, expr: SimpleValueConst) => - SimpleValueConstExprInterpreter.interpretExpr(target, expr) - case Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(target, expr) + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) @@ -56,8 +51,8 @@ class L0InterpretationHandler()( case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, target, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(target, expr) + case stmt @ Assignment(_, target, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) @@ -71,9 +66,5 @@ class L0InterpretationHandler()( object L0InterpretationHandler { - def apply()( - implicit - p: SomeProject, - ps: PropertyStore - ): L0InterpretationHandler = new L0InterpretationHandler + def apply(project: SomeProject): L0InterpretationHandler = new L0InterpretationHandler()(project) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index a19c76ffe4..0d01ca53f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -30,7 +30,7 @@ object L0NonVirtualMethodCallInterpreter extends StringInterpreter { case 0 => computeFinalResult(IdentityFlow) case _ => - val targetVar = init.receiver.asVar.toPersistentForm(state.tac.stmts) + val targetVar = StringInterpreter.findUVarForDVar(init.receiver.asVar).toPersistentForm(state.tac.stmts) // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val paramVar = init.params.head.asVar.toPersistentForm(state.tac.stmts) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 54cae19ad8..b5ec2b0ac1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -29,7 +29,7 @@ case class L0StaticFunctionCallInterpreter()( override type E = StaticFunctionCall[V] - override def interpretExpr(target: V, call: E)(implicit + override def interpretExpr(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { call.name match { @@ -47,7 +47,7 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter override type E <: StaticFunctionCall[V] - def interpretArbitraryCall(target: V, call: E)(implicit + def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { val calleeMethod = call.resolveCallTarget(state.dm.definedMethod.classFile.thisType) @@ -56,9 +56,8 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter } val m = calleeMethod.value - val pt = target.toPersistentForm(state.tac.stmts) val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) - val callState = FunctionCallState(state, pt, Seq(m), params, Map((m, ps(m, TACAI.key)))) + val callState = FunctionCallState(state, target, Seq(m), params, Map((m, ps(m, TACAI.key)))) interpretArbitraryCallToFunctions(callState) } @@ -68,8 +67,9 @@ private[string] trait L0StringValueOfFunctionCallInterpreter extends AssignmentB override type E <: StaticFunctionCall[V] - def processStringValueOf(target: V, call: E)(implicit state: InterpretationState): ProperPropertyComputationResult = { - val pt = target.toPersistentForm(state.tac.stmts) + def processStringValueOf(target: PV, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { val pp = call.params.head.asVar.toPersistentForm(state.tac.stmts) val flowFunction: StringFlowFunction = if (call.descriptor.parameterType(0).toJava == "char") { @@ -77,13 +77,13 @@ private[string] trait L0StringValueOfFunctionCallInterpreter extends AssignmentB { env(pp) match { case const: StringTreeConst if const.isIntConst => - env.update(pt, StringTreeConst(const.string.toInt.toChar.toString)) + env.update(target, StringTreeConst(const.string.toInt.toChar.toString)) case tree => - env.update(pt, tree) + env.update(target, tree) } } } else { - (env: StringTreeEnvironment) => env.update(pt, env(pp)) + (env: StringTreeEnvironment) => env.update(target, env(pp)) } computeFinalResult(flowFunction) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 869367b994..7d9a3b6669 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -43,7 +43,9 @@ case class L0VirtualFunctionCallInterpreter() override def interpretExpr(instr: T, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - val at = Option.unless(!instr.isAssignment)(instr.asAssignment.targetVar.asVar.toPersistentForm(state.tac.stmts)) + val at = Option.unless(!instr.isAssignment) { + StringInterpreter.findUVarForDVar(instr.asAssignment.targetVar.asVar).toPersistentForm(state.tac.stmts) + } val pt = call.receiver.asVar.toPersistentForm(state.tac.stmts) call.name match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 5b1265dae7..51dd3d25e9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -8,7 +8,6 @@ package l1 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore @@ -28,12 +27,15 @@ object L1StringAnalysis { object LazyL1StringAnalysis extends LazyStringAnalysis { + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = new L1StringAnalysis(p) +} + +object LazyL1StringFlowAnalysis extends LazyStringFlowAnalysis { + override final def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Callees)) ++ super.uses - override final def init(p: SomeProject, ps: PropertyStore): InitializationData = - (new L1StringAnalysis(p), L1InterpretationHandler(p, ps)) + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = L1InterpretationHandler(p) - override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) ++ - L1InterpretationHandler.requiredProjectInformation ++ - super.requiredProjectInformation + override def requiredProjectInformation: ProjectInformationKeys = super.requiredProjectInformation ++ + L1InterpretationHandler.requiredProjectInformation } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 506be61be1..87abf46ea4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -11,7 +11,6 @@ import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNode @@ -95,12 +94,12 @@ case class L1FieldReadInterpreter( /** * Currently, fields are approximated using the following approach: If a field of a type not supported by the - * [[L1StringAnalysis]] is passed, [[StringConstancyInformation.lb]] will be produces. Otherwise, all write accesses + * [[L1StringAnalysis]] is passed, a flow function producing the LB will be produced. 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 * [[org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC]]. */ - override def interpretExpr(target: V, fieldRead: E)(implicit + override def interpretExpr(target: PV, fieldRead: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { if (!StringAnalysis.isSupportedType(fieldRead.declaredFieldType)) { @@ -114,16 +113,15 @@ case class L1FieldReadInterpreter( return computeFinalLBFor(target) } - val persistentTarget = target.toPersistentForm(state.tac.stmts) if (writeAccesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value return computeFinalResult(ConstantResultFlow.forVariable( - persistentTarget, + target, StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) )) } - implicit val accessState: FieldReadState = FieldReadState(persistentTarget) + implicit val accessState: FieldReadState = FieldReadState(target) writeAccesses.foreach { case (contextId, _, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 1936d0a2f8..99b5717ba9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -16,7 +16,6 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter @@ -31,10 +30,7 @@ import org.opalj.tac.fpcf.properties.string.IdentityFlow * * @author Maximilian Rüsch */ -class L1InterpretationHandler( - implicit val p: SomeProject, - implicit val ps: PropertyStore -) extends InterpretationHandler { +class L1InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) @@ -49,8 +45,8 @@ class L1InterpretationHandler( } state.tac.stmts(defSiteOpt.get) match { - case Assignment(_, target, expr: SimpleValueConst) => - SimpleValueConstExprInterpreter.interpretExpr(target, expr) + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) @@ -58,9 +54,9 @@ class L1InterpretationHandler( case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(IdentityFlow) - case Assignment(_, targetVar, expr: FieldRead[V]) => + case stmt @ Assignment(_, targetVar, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( - targetVar, + stmt, expr ) // Field reads without result usage are irrelevant @@ -76,13 +72,13 @@ class L1InterpretationHandler( case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case Assignment(_, target, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(target, expr) + case stmt @ Assignment(_, target, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(target, expr) + case stmt @ Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) @@ -99,6 +95,5 @@ object L1InterpretationHandler { def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, FieldAccessInformationKey, ContextProviderKey) - def apply(project: SomeProject, ps: PropertyStore): L1InterpretationHandler = - new L1InterpretationHandler()(project, ps) + def apply(project: SomeProject): L1InterpretationHandler = new L1InterpretationHandler()(project) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/Path.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/Path.scala deleted file mode 100644 index 72f67b9f5c..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/Path.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string -package preprocessing - -/** - * A flat element, e.g., for representing a single statement. The statement is identified with `pc` by its [[org.opalj.br.PC]]. - * - * @author Maximilian Rüsch - */ -class PathElement private[PathElement] (val pc: Int) { - - override def toString: String = s"PathElement(pc=$pc)" - - def stmtIndex(implicit pcToIndex: Array[Int]): Int = valueOriginOfPC(pc, pcToIndex).get -} - -object PathElement { - def apply(defSite: Int)(implicit stmts: Array[Stmt[V]]) = new PathElement(pcOfDefSite(defSite)) - - def unapply(fpe: PathElement)(implicit pcToIndex: Array[Int]): Some[Int] = Some(fpe.stmtIndex) - - def fromPC(pc: Int) = new PathElement(pc) -} - -case class Path(elements: List[PathElement]) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala deleted file mode 100644 index a80cea3a77..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/preprocessing/PathFinder.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string -package preprocessing - -import org.opalj.br.fpcf.properties.string.StringConstancyInformation -import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string.StringTreeNode -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler - -object PathFinder { - - private def evaluatePathElement(pe: PathElement)(implicit - state: ComputationState, - ps: PropertyStore - ): Option[StringConstancyInformation] = { - val sci = ps( - InterpretationHandler.getEntityForPC(pe.pc, state.dm, state.tac, state.entity._1), - StringConstancyProperty.key - ) match { - case UBP(scp) => scp.sci - case _ => StringConstancyInformation.lb - } - Option.unless(sci.isTheNeutralElement)(sci) - } - - def findPath(value: V, tac: TAC): Option[Path] = { - if (FlowGraph(tac.cfg).isCyclic || value.definedBy.size > 1) { - return None; - } - - val defSite = value.definedBy.head - val allDefAndUseSites = tac.stmts(defSite).asAssignment.targetVar.usedBy.+(defSite).toList.sorted - - Some(Path(allDefAndUseSites.map(PathElement.apply(_)(tac.stmts)))) - } - - def transformPath(path: Path, tac: TAC)(implicit - state: ComputationState, - ps: PropertyStore - ): StringTreeNode = { - path.elements.size match { - case 1 => - evaluatePathElement(path.elements.head).map(_.tree).getOrElse(StringTreeDynamicString) - case _ => - // IMPROVE handle entire control tree here - val evaluatedElements = path.elements.flatMap(evaluatePathElement) - evaluatedElements.foldLeft[StringTreeNode](StringTreeNeutralElement) { - (currentState, nextSci) => nextSci.treeFn(currentState) - } - } - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index f9639549e4..94c1dbd2d4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -34,7 +34,7 @@ object StringFlowFunction extends StringFlowFunctionPropertyMetaInformation { override val key: PropertyKey[StringFlowFunction] = PropertyKey.create(propertyName) - def ub: StringFlowFunction = ConstantResultFlow.forAll(StringTreeNeutralElement) + def ub: StringFlowFunction = ConstantResultFlow.forAll(StringTreeNeutralElement) // TODO should this be the real bottom element? def ub(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeNeutralElement) def lb(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeDynamicString) def noFlow(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeInvalidElement) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 0725a1c079..89ccd359d0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -7,6 +7,7 @@ package string import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr /** * @author Maximilian Rüsch @@ -20,7 +21,16 @@ case class StringTreeEnvironment(private val map: Map[PV, StringTreeNode]) { def updateAll(value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.map { kv => (kv._1, value) }) def join(other: StringTreeEnvironment): StringTreeEnvironment = { - this // TODO implement join here + val result = (map.keySet ++ other.map.keys) map { pv => + (map.get(pv), other.map.get(pv)) match { + case (Some(value1), Some(value2)) => pv -> StringTreeOr.fromNodes(value1, value2) + case (Some(value1), None) => pv -> value1 + case (None, Some(value2)) => pv -> value2 + case (None, None) => throw new IllegalStateException("Found a key in a map that is not in the map!") + } + } + + StringTreeEnvironment(result.toMap) } } From d039725101c55d875e21efee166f10ca0969ba07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 20 May 2024 22:08:18 +0200 Subject: [PATCH 427/583] Extend environments by webs --- .../info/StringAnalysisReflectiveCalls.scala | 23 ++- .../org/opalj/fpcf/StringAnalysisTest.scala | 52 +++---- .../src/main/scala/org/opalj/tac/DUVar.scala | 4 +- .../src/main/scala/org/opalj/tac/DUWeb.scala | 30 ++++ .../src/main/scala/org/opalj/tac/PDUVar.scala | 41 +++++- .../src/main/scala/org/opalj/tac/PDUWeb.scala | 43 ++++++ .../analyses/string/ComputationState.scala | 20 ++- .../fpcf/analyses/string/StringAnalysis.scala | 48 +++--- .../analyses/string/StringInterpreter.scala | 36 +++-- .../BinaryExprInterpreter.scala | 13 +- .../InterpretationHandler.scala | 9 +- .../SimpleValueConstExprInterpreter.scala | 20 ++- .../L0FunctionCallInterpreter.scala | 14 +- .../L0InterpretationHandler.scala | 12 +- .../L0NonVirtualMethodCallInterpreter.scala | 14 +- .../L0StaticFunctionCallInterpreter.scala | 11 +- .../L0VirtualFunctionCallInterpreter.scala | 138 ++++++++++-------- .../L0VirtualMethodCallInterpreter.scala | 4 +- .../L1FieldReadInterpreter.scala | 20 ++- .../L1InterpretationHandler.scala | 19 ++- .../L1VirtualFunctionCallInterpreter.scala | 4 +- .../tac/fpcf/analyses/string/package.scala | 2 +- .../string/StringFlowFunction.scala | 63 ++++++-- .../string/StringTreeEnvironment.scala | 30 ++-- 24 files changed, 427 insertions(+), 243 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.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 index 382511781e..3255a3b0f5 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 @@ -180,6 +180,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * analysis using the property store, `ps`, to finally store it in the given `resultMap`. */ private def processFunctionCall( + pc: Int, ps: PropertyStore, method: Method, call: Call[V], @@ -196,11 +197,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { call.descriptor.parameterTypes.zipWithIndex.foreach { case (ft, index) => if (ft.toJava == "java.lang.String") { - val e = (call.params(index).asVar.toPersistentForm, method) + val e = (pc, call.params(index).asVar.toPersistentForm, method) ps.force(e, StringConstancyProperty.key) - entityContext.append( - (e, buildFQMethodName(call.declaringClass, call.name)) - ) + entityContext.append((e, buildFQMethodName(call.declaringClass, call.name))) } } } @@ -244,17 +243,17 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { // 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 Assignment(pc, _, c: StaticFunctionCall[V]) => + processFunctionCall(pc, ps, m, c, resultMap) + case Assignment(pc, _, c: VirtualFunctionCall[V]) => + processFunctionCall(pc, 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 ExprStmt(pc, c: StaticFunctionCall[V]) => + processFunctionCall(pc, ps, m, c, resultMap) + case ExprStmt(pc, c: VirtualFunctionCall[V]) => + processFunctionCall(pc, ps, m, c, resultMap) case _ => } case _ => 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 3ef94e6a6d..ec0e1e7fde 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 @@ -45,7 +45,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, * [[extractPUVars]] is called with their [[TACode]]. */ - def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(SEntity, Method)] = { + def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(Int, SEntity, Method)] = { val tacProvider = project.get(EagerDetachedTACAIKey) project.classHierarchy.allSuperclassesIterator( ObjectType(StringAnalysisTest.getAllowedFQTestMethodObjectTypeNameForLevel(level)), @@ -61,8 +61,8 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } - .foldLeft(Seq.empty[(SEntity, Method)]) { (entities, m) => - entities ++ extractPUVars(tacProvider(m)).map((_, m)) + .foldLeft(Seq.empty[(Int, SEntity, Method)]) { (entities, m) => + entities ++ extractPUVars(tacProvider(m)).map(e => (e._1, e._2, m)) } } @@ -72,25 +72,25 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { * * @return Returns the arguments of the [[nameTestMethod]] as a PUVars list in the order in which they occurred. */ - def extractPUVars(tac: TACode[TACMethodParameter, V]): List[SEntity] = { + def extractPUVars(tac: TACode[TACMethodParameter, V]): List[(Int, SEntity)] = { tac.cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) => allowedFQTestMethodsClassNames.exists(_ == declClass.toJavaClass.getName) && name == nameTestMethod case _ => false - }.map(_.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)).toList + }.map { call => (call.pc, call.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)) }.toList } def determineEAS( - entities: Iterable[(SEntity, Method)], + entities: Iterable[(Int, SEntity, Method)], project: Project[URL] - ): Iterable[((SEntity, Method), String => String, List[Annotation])] = { - val m2e = entities.groupBy(_._2).iterator.map(e => e._1 -> e._2.map(k => k._1)).toMap + ): Iterable[((Int, SEntity, Method), String => String, List[Annotation])] = { + val m2e = entities.groupBy(_._3).iterator.map(e => e._1 -> e._2.map(k => (k._1, k._2))).toMap // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => m2e(am._1).zipWithIndex.map { - case (puVar, index) => + case ((pc, puVar), index) => Tuple3( - (puVar, am._1), + (pc, puVar, am._1), { s: String => s"${am._2(s)} (#$index)" }, List(StringAnalysisTest.getStringDefinitionsFromCollection(am._3, index)) ) @@ -162,9 +162,9 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities - .filter(entity => entity._2.name.startsWith("simpleStringConcat2")) + .filter(entity => entity._3.name.startsWith("simpleStringConcat2")) // Currently broken L0 Tests - .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) + .filterNot(entity => entity._3.name.startsWith("unknownCharValue")) it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) @@ -201,23 +201,23 @@ class L1StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) // L0 Tests - .filterNot(entity => entity._2.name == "simpleStringConcat") // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" + .filterNot(entity => entity._3.name == "simpleStringConcat") // Waits on string_concat and "substring" + .filterNot(entity => entity._3.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" // Currently broken L0 Tests - .filterNot(entity => entity._2.name.startsWith("unknownCharValue")) + .filterNot(entity => entity._3.name.startsWith("unknownCharValue")) // L1 Tests - .filterNot(entity => entity._2.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" - .filterNot(entity => entity._2.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" + .filterNot(entity => entity._3.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" + .filterNot(entity => entity._3.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" + .filterNot(entity => entity._3.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" // Currently broken L1 Tests - .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) - .filterNot(entity => entity._2.name.startsWith("unknownHierarchyInstanceTest")) - .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest1")) - .filterNot(entity => entity._2.name.startsWith("severalReturnValuesTest2")) - .filterNot(entity => entity._2.name.startsWith("crissCrossExample")) - .filterNot(entity => entity._2.name.startsWith("directAppendConcatsWith2ndStringBuilder")) - .filterNot(entity => entity._2.name.startsWith("parameterRead")) - .filterNot(entity => entity._2.name.startsWith("fromStringArray")) + .filterNot(entity => entity._3.name.startsWith("cyclicDependencyTest")) + .filterNot(entity => entity._3.name.startsWith("unknownHierarchyInstanceTest")) + .filterNot(entity => entity._3.name.startsWith("severalReturnValuesTest1")) + .filterNot(entity => entity._3.name.startsWith("severalReturnValuesTest2")) + .filterNot(entity => entity._3.name.startsWith("crissCrossExample")) + .filterNot(entity => entity._3.name.startsWith("directAppendConcatsWith2ndStringBuilder")) + .filterNot(entity => entity._3.name.startsWith("parameterRead")) + .filterNot(entity => entity._3.name.startsWith("fromStringArray")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala index 1f43f1c209..f9dbe87e4b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala @@ -185,7 +185,9 @@ class DVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ s"DVar(useSites=${useSites.mkString("{", ",", "}")},value=$value,origin=$origin)" } - override def toPersistentForm(implicit stmts: Array[Stmt[V]]): Nothing = throw new UnsupportedOperationException + override def toPersistentForm( + implicit stmts: Array[Stmt[V]] + ): PDVar[Value] = PDVar(value, usedBy.map(pcOfDefSite _)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala new file mode 100644 index 0000000000..3c3dfe1eb1 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala @@ -0,0 +1,30 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac + +import org.opalj.collection.immutable.IntTrieSet +import org.opalj.value.ValueInformation + +case class DUWeb( + defSites: IntTrieSet, + useSites: IntTrieSet +) { + def intersectsWith(other: DUWeb): Boolean = { + other.defSites.intersect(defSites).nonEmpty && other.useSites.intersect(useSites).nonEmpty + } + + def intersect(other: DUWeb): DUWeb = DUWeb(other.defSites.intersect(defSites), other.useSites.intersect(useSites)) + + def toPersistentForm(implicit stmts: Array[Stmt[V]]): PDUWeb = PDUWeb( + defSites.map(pcOfDefSite _), + useSites.map(pcOfDefSite _) + ) +} + +object DUWeb { + def forDVar[Value <: ValueInformation](defSite: Int, dVar: DVar[Value]): DUWeb = + DUWeb(IntTrieSet(defSite), dVar.useSites) + + def forUVar[Value <: ValueInformation](useSite: Int, uVar: UVar[Value]): DUWeb = + DUWeb(uVar.defSites, IntTrieSet(useSite)) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala index 6a140cb6ff..cf8362a052 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala @@ -35,6 +35,41 @@ abstract class PDUVar[+Value <: ValueInformation] { def toValueOriginForm(implicit pcToIndex: Array[Int]): DUVar[Value] } +class PDVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( + val value: Value, + val usePCs: PCs +) extends PDUVar[Value] { + + def defPCs: Nothing = throw new UnsupportedOperationException + + override def equals(other: Any): Boolean = { + other match { + case that: PDVar[_] => this.usePCs == that.usePCs + case _ => false + } + } + + override def toString: String = { + s"PDVar(usePCs=${usePCs.mkString("{", ",", "}")},value=$value)" + } + + def toValueOriginForm(implicit pcToIndex: Array[Int]): Nothing = throw new UnsupportedOperationException +} + +object PDVar { + + def apply(d: org.opalj.ai.ValuesDomain)( + value: d.DomainValue, + usePCs: PCs + ): PDVar[d.DomainValue] = { + new PDVar[d.DomainValue](value, usePCs) + } + + def apply[Value <: ValueInformation](value: Value, useSites: IntTrieSet): PDVar[Value] = new PDVar(value, useSites) + + def unapply[Value <: ValueInformation](d: PDVar[Value]): Some[(Value, IntTrieSet)] = Some((d.value, d.usePCs)) +} + class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( val value: Value, val defPCs: PCs @@ -50,7 +85,7 @@ class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ } override def toString: String = { - s"PUVar(defSites=${defPCs.mkString("{", ",", "}")},value=$value)" + s"PUVar(defPCs=${defPCs.mkString("{", ",", "}")},value=$value)" } override def toValueOriginForm( @@ -67,7 +102,7 @@ object PUVar { new PUVar[d.DomainValue](value, defPCs) } - def apply[Value <: ValueInformation](value: Value, defSites: IntTrieSet): PUVar[Value] = new PUVar(value, defSites) + def apply[Value <: ValueInformation](value: Value, defPCs: IntTrieSet): PUVar[Value] = new PUVar(value, defPCs) - def unapply[Value <: ValueInformation](u: UVar[Value]): Some[(Value, IntTrieSet)] = Some((u.value, u.defSites)) + def unapply[Value <: ValueInformation](u: PUVar[Value]): Some[(Value, IntTrieSet)] = Some((u.value, u.defPCs)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala new file mode 100644 index 0000000000..5e9f1d510e --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac + +import org.opalj.collection.immutable.IntTrieSet +import org.opalj.value.ValueInformation + +case class PDUWeb( + defPCs: IntTrieSet, + usePCs: IntTrieSet +) { + def containsVarAt(pc: Int, pv: PV): Boolean = pv match { + case PDVar(_, varUsePCs) => defPCs.contains(pc) && usePCs.intersect(varUsePCs).nonEmpty + case PUVar(_, varDefPCs) => usePCs.contains(pc) && defPCs.intersect(varDefPCs).nonEmpty + } + + def intersectsWith(other: PDUWeb): Boolean = { + other.defPCs.intersect(defPCs).nonEmpty && other.usePCs.intersect(usePCs).nonEmpty + } + + def intersect(other: PDUWeb): PDUWeb = PDUWeb(other.defPCs.intersect(defPCs), other.usePCs.intersect(usePCs)) + + def size(): Int = defPCs.size + usePCs.size + + def toValueOriginForm(implicit pcToIndex: Array[Int]): DUWeb = DUWeb( + valueOriginsOfPCs(defPCs, pcToIndex), + valueOriginsOfPCs(usePCs, pcToIndex) + ) +} + +object PDUWeb { + + def apply(pc: Int, pv: PV): PDUWeb = pv match { + case pdVar: PDVar[_] => forDVar(pc, pdVar) + case puVar: PUVar[_] => forUVar(pc, puVar) + } + + def forDVar[Value <: ValueInformation](defPC: Int, pdVar: PDVar[Value]): PDUWeb = + PDUWeb(IntTrieSet(defPC), pdVar.usePCs) + + def forUVar[Value <: ValueInformation](usePC: Int, puVar: PUVar[Value]): PDUWeb = + PDUWeb(puVar.defPCs, IntTrieSet(usePC)) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 65aac6a203..9e8dc26521 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -16,7 +16,7 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction -import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * This class is to be used to store state information that are required at a later point in @@ -32,27 +32,33 @@ case class ComputationState(dm: DefinedMethod, entity: SContext, var tacDependee throw new IllegalStateException("Cannot get a TAC from a TACAI with no or empty upper bound!") } - var startEnv: StringTreeEnvironment = _ var flowGraph: FlowGraph = _ var superFlowGraph: SuperFlowGraph = _ var controlTree: ControlTree = _ - private val pcToDependeeMapping: mutable.Map[Int, EOptionP[MethodPC, StringFlowFunction]] = mutable.Map.empty + private val pcToDependeeMapping: mutable.Map[Int, EOptionP[MethodPC, StringFlowFunctionProperty]] = + mutable.Map.empty - def updateDependee(pc: Int, dependee: EOptionP[MethodPC, StringFlowFunction]): Unit = + def updateDependee(pc: Int, dependee: EOptionP[MethodPC, StringFlowFunctionProperty]): Unit = pcToDependeeMapping.update(pc, dependee) - def dependees: Set[EOptionP[MethodPC, StringFlowFunction]] = pcToDependeeMapping.values.filter(_.isRefinable).toSet + def dependees: Set[EOptionP[MethodPC, StringFlowFunctionProperty]] = + pcToDependeeMapping.values.filter(_.isRefinable).toSet def hasDependees: Boolean = pcToDependeeMapping.valuesIterator.exists(_.isRefinable) def getFlowFunctionsByPC: Map[Int, StringFlowFunction] = pcToDependeeMapping.map { kv => ( kv._1, - if (kv._2.hasUBP) kv._2.ub - else StringFlowFunction.ub + if (kv._2.hasUBP) kv._2.ub.flow + else StringFlowFunctionProperty.ub.flow ) }.toMap + + def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.valuesIterator.flatMap { v => + if (v.hasUBP) v.ub.webs + else StringFlowFunctionProperty.ub.webs + } } case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 01d5d70bc7..adf2e53ae9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -36,7 +36,7 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** @@ -47,7 +47,7 @@ trait StringAnalysis extends FPCFAnalysis { val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(data: SContext): ProperPropertyComputationResult = { - val state = ComputationState(declaredMethods(data._2), data, ps(data._2, TACAI.key)) + val state = ComputationState(declaredMethods(data._3), data, ps(data._3, TACAI.key)) if (state.tacDependee.isRefinable) { InterimResult( @@ -73,20 +73,6 @@ trait StringAnalysis extends FPCFAnalysis { private def determinePossibleStrings(implicit state: ComputationState): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac - state.startEnv = StringTreeEnvironment(Map.empty.withDefault { pv: PV => - val defPCs = pv.defPCs.toList.sorted - if (defPCs.head >= 0) { - StringTreeNeutralElement - } else { - val pc = defPCs.head - if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - StringTreeDynamicString - } else { - StringTreeParameter.forParameterPC(pc) - } - } - }) - state.flowGraph = FlowGraph(tac.cfg) val (_, superFlowGraph, controlTree) = StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) @@ -95,7 +81,7 @@ trait StringAnalysis extends FPCFAnalysis { state.flowGraph.nodes.toOuter.foreach { case Statement(pc) if pc >= 0 => - state.updateDependee(pc, propertyStore(MethodPC(pc, state.dm), StringFlowFunction.key)) + state.updateDependee(pc, propertyStore(MethodPC(pc, state.dm), StringFlowFunctionProperty.key)) case _ => } @@ -109,8 +95,8 @@ trait StringAnalysis extends FPCFAnalysis { state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] determinePossibleStrings(state) - case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunction.key) => - state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunction]]) + case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunctionProperty.key) => + state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunctionProperty]]) computeResults(state) case _ => @@ -137,13 +123,27 @@ trait StringAnalysis extends FPCFAnalysis { } private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { + val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => + val defPCs = web.defPCs.toList.sorted + if (defPCs.head >= 0) { + (web, StringTreeNeutralElement) + } else { + val pc = defPCs.head + if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { + (web, StringTreeDynamicString) + } else { + (web, StringTreeParameter.forParameterPC(pc)) + } + } + }.toMap) + val resultEnv = DataFlowAnalysis.compute( state.controlTree, state.superFlowGraph, state.getFlowFunctionsByPC - )(state.startEnv) + )(startEnv) - StringConstancyProperty(StringConstancyInformation(resultEnv(state.entity._1))) + StringConstancyProperty(StringConstancyInformation(resultEnv(state.entity._1, state.entity._2))) } } @@ -196,7 +196,7 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), - PropertyBounds.ub(StringFlowFunction) + PropertyBounds.ub(StringFlowFunctionProperty) ) override final type InitializationData = StringAnalysis @@ -224,7 +224,7 @@ trait LazyStringAnalysis sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunction) + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunctionProperty) override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) @@ -241,7 +241,7 @@ trait LazyStringFlowAnalysis extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(StringFlowFunction.key, initData.analyze) + ps.registerLazyPropertyComputation(StringFlowFunctionProperty.key, initData.analyze) initData } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 428ec3c44d..fb6b0a35e0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -10,6 +10,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch @@ -31,8 +32,14 @@ trait StringInterpreter { def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = StringInterpreter.computeFinalLBFor(v) - def computeFinalResult(sff: StringFlowFunction)(implicit state: InterpretationState): Result = - StringInterpreter.computeFinalResult(sff) + def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalResult(web, sff) + + def computeFinalResult(webs: Set[PDUWeb], sff: StringFlowFunction)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalResult(webs, sff) + + def computeFinalResult(p: StringFlowFunctionProperty)(implicit state: InterpretationState): Result = + StringInterpreter.computeFinalResult(p) } object StringInterpreter { @@ -41,25 +48,16 @@ object StringInterpreter { computeFinalLBFor(v.toPersistentForm(state.tac.stmts)) def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = - computeFinalResult(StringFlowFunction.lb(v)) - - def computeFinalResult(sff: StringFlowFunction)(implicit state: InterpretationState): Result = - Result(FinalEP(InterpretationHandler.getEntity(state), sff)) + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, v)) - def findUVarForDVar(dVar: V)(implicit state: InterpretationState): V = { - state.tac.stmts(dVar.usedBy.head) match { - case Assignment(_, _, expr: Var[V]) => expr.asVar - case ExprStmt(_, expr: Var[V]) => expr.asVar + def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = + Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(web, sff))) - case Assignment(_, _, call: InstanceFunctionCall[V]) => call.receiver.asVar - case ExprStmt(_, call: InstanceFunctionCall[V]) => call.receiver.asVar + def computeFinalResult(webs: Set[PDUWeb], sff: StringFlowFunction)(implicit state: InterpretationState): Result = + Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(webs, sff))) - case ExprStmt(_, call: InstanceMethodCall[V]) => call.receiver.asVar - - case _ => - throw new IllegalArgumentException(s"Cannot determine uVar from $dVar") - } - } + def computeFinalResult(p: StringFlowFunctionProperty)(implicit state: InterpretationState): Result = + Result(FinalEP(InterpretationHandler.getEntity(state), p)) } trait ParameterEvaluatingStringInterpreter extends StringInterpreter { @@ -92,7 +90,7 @@ trait AssignmentBasedStringInterpreter extends AssignmentLikeBasedStringInterpre override final def interpretExpr(instr: T, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - interpretExpr(StringInterpreter.findUVarForDVar(instr.targetVar).toPersistentForm(state.tac.stmts), expr) + interpretExpr(instr.targetVar.toPersistentForm(state.tac.stmts), expr) } def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala index d34b52aeff..7cfde1ea06 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala @@ -11,8 +11,7 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch @@ -27,15 +26,17 @@ object BinaryExprInterpreter extends AssignmentBasedStringInterpreter { *
  • [[ComputationalTypeInt]] *
  • [[ComputationalTypeFloat]]
  • * - * For all other expressions, [[IdentityFlow]] will be returned. + * For all other expressions, [[StringFlowFunctionProperty.identity]] will be returned. */ override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { computeFinalResult(expr.cTpe match { - case ComputationalTypeInt => ConstantResultFlow.forVariable(target, StringTreeDynamicInt) - case ComputationalTypeFloat => ConstantResultFlow.forVariable(target, StringTreeDynamicFloat) - case _ => IdentityFlow + case ComputationalTypeInt => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicInt) + case ComputationalTypeFloat => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicFloat) + case _ => StringFlowFunctionProperty.identity }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index b77bcd77f6..447908d3e4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -15,8 +15,7 @@ import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow -import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site @@ -36,13 +35,13 @@ abstract class InterpretationHandler extends FPCFAnalysis { if (tacaiEOptP.isRefinable) { InterimResult.forUB( InterpretationHandler.getEntity, - StringFlowFunction.ub, + StringFlowFunctionProperty.ub, Set(state.tacDependee), continuation(state) ) } else if (tacaiEOptP.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - StringInterpreter.computeFinalResult(ConstantResultFlow.forAll(StringTreeNode.lb)) + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.constForAll(StringTreeNode.lb)) } else { processNew } @@ -58,7 +57,7 @@ abstract class InterpretationHandler extends FPCFAnalysis { case _ => InterimResult.forUB( InterpretationHandler.getEntity(state), - StringFlowFunction.ub, + StringFlowFunctionProperty.ub, Set(state.tacDependee), continuation(state) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala index 8f09ab64c3..0262c43b00 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala @@ -8,8 +8,7 @@ package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch @@ -22,12 +21,17 @@ object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter state: InterpretationState ): ProperPropertyComputationResult = { computeFinalResult(expr match { - case ic: IntConst => ConstantResultFlow.forVariable(target, StringTreeConst(ic.value.toString)) - case fc: FloatConst => ConstantResultFlow.forVariable(target, StringTreeConst(fc.value.toString)) - case dc: DoubleConst => ConstantResultFlow.forVariable(target, StringTreeConst(dc.value.toString)) - case lc: LongConst => ConstantResultFlow.forVariable(target, StringTreeConst(lc.value.toString)) - case sc: StringConst => ConstantResultFlow.forVariable(target, StringTreeConst(sc.value)) - case _ => IdentityFlow + case ic: IntConst => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(ic.value.toString)) + case fc: FloatConst => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(fc.value.toString)) + case dc: DoubleConst => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(dc.value.toString)) + case lc: LongConst => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(lc.value.toString)) + case sc: StringConst => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(sc.value)) + case _ => StringFlowFunctionProperty.identity }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index 67fec8671c..633bd73a25 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -20,6 +20,7 @@ import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** @@ -85,6 +86,7 @@ trait L0FunctionCallInterpreter } else { callState.returnDependees += m -> returns.map { ret => val entity: SContext = ( + ret.pc, ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m ) @@ -102,15 +104,17 @@ trait L0FunctionCallInterpreter if (callState.hasDependees) { InterimResult.forUB( InterpretationHandler.getEntity(callState.state), - StringFlowFunction.ub, + StringFlowFunctionProperty.ub, callState.dependees.toSet, continuation(callState) ) } else { + val pc = callState.state.pc val parameters = callState.parameters.zipWithIndex.map(x => (x._2, x._1)).toMap val flowFunction: StringFlowFunction = (env: StringTreeEnvironment) => env.update( + pc, callState.target, StringTreeNode.reduceMultiple { callState.calleeMethods.map { m => @@ -118,14 +122,16 @@ trait L0FunctionCallInterpreter StringTreeNode.lb } else { StringTreeNode.reduceMultiple(callState.returnDependees(m).map { - _.asFinal.p.sci.tree.replaceParameters(parameters.map { kv => (kv._1, env(kv._2)) }) + _.asFinal.p.sci.tree.replaceParameters(parameters.map { kv => + (kv._1, env(pc, kv._2)) + }) }) } } } ) - computeFinalResult(flowFunction)(callState.state) + computeFinalResult(PDUWeb(pc, callState.target), flowFunction)(callState.state) } } @@ -137,7 +143,7 @@ trait L0FunctionCallInterpreter case EUBP(_, _: StringConstancyProperty) => val contextEPS = eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]] - callState.updateReturnDependee(contextEPS.e._2, contextEPS) + callState.updateReturnDependee(contextEPS.e._3, contextEPS) tryComputeFinalResult(callState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 7e19516131..4a0e981207 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -12,7 +12,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc @@ -39,7 +39,7 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.computeFinalLBFor(target) case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(IdentityFlow) + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) @@ -51,15 +51,17 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ Assignment(_, target, expr: StaticFunctionCall[V]) => + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant - case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) - case _ => StringInterpreter.computeFinalResult(IdentityFlow) + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index 0d01ca53f6..ae4ae3fdf2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -8,7 +8,7 @@ package l0 package interpretation import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** @@ -21,20 +21,24 @@ object L0NonVirtualMethodCallInterpreter extends StringInterpreter { override def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { instr.name match { case "" => interpretInit(instr) - case _ => computeFinalResult(IdentityFlow) + case _ => computeFinalResult(StringFlowFunctionProperty.identity) } } private def interpretInit(init: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { init.params.size match { case 0 => - computeFinalResult(IdentityFlow) + computeFinalResult(StringFlowFunctionProperty.identity) case _ => - val targetVar = StringInterpreter.findUVarForDVar(init.receiver.asVar).toPersistentForm(state.tac.stmts) + val pc = state.pc + val targetVar = init.receiver.asVar.toPersistentForm(state.tac.stmts) // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val paramVar = init.params.head.asVar.toPersistentForm(state.tac.stmts) - computeFinalResult((env: StringTreeEnvironment) => env.update(targetVar, env(paramVar))) + computeFinalResult( + Set(PDUWeb(pc, targetVar), PDUWeb(pc, paramVar)), + (env: StringTreeEnvironment) => env.update(pc, targetVar, env(pc, paramVar)) + ) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index b5ec2b0ac1..0a03cadffe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -70,22 +70,23 @@ private[string] trait L0StringValueOfFunctionCallInterpreter extends AssignmentB def processStringValueOf(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { + val pc = state.pc val pp = call.params.head.asVar.toPersistentForm(state.tac.stmts) val flowFunction: StringFlowFunction = if (call.descriptor.parameterType(0).toJava == "char") { (env: StringTreeEnvironment) => { - env(pp) match { + env(pc, pp) match { case const: StringTreeConst if const.isIntConst => - env.update(target, StringTreeConst(const.string.toInt.toChar.toString)) + env.update(pc, target, StringTreeConst(const.string.toInt.toChar.toString)) case tree => - env.update(target, tree) + env.update(pc, target, tree) } } } else { - (env: StringTreeEnvironment) => env.update(target, env(pp)) + (env: StringTreeEnvironment) => env.update(pc, target, env(pc, pp)) } - computeFinalResult(flowFunction) + computeFinalResult(Set(PDUWeb(pc, pp), PDUWeb(pc, target)), flowFunction) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 7d9a3b6669..73b7a1ed6f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -20,9 +20,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow -import org.opalj.tac.fpcf.properties.string.IdentityFlow -import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue @@ -43,9 +41,7 @@ case class L0VirtualFunctionCallInterpreter() override def interpretExpr(instr: T, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - val at = Option.unless(!instr.isAssignment) { - StringInterpreter.findUVarForDVar(instr.asAssignment.targetVar.asVar).toPersistentForm(state.tac.stmts) - } + val at = Option.unless(!instr.isAssignment)(instr.asAssignment.targetVar.asVar.toPersistentForm(state.tac.stmts)) val pt = call.receiver.asVar.toPersistentForm(state.tac.stmts) call.name match { @@ -58,13 +54,21 @@ case class L0VirtualFunctionCallInterpreter() call.descriptor.returnType match { case obj: ObjectType if obj == ObjectType.String => if (at.isDefined) interpretArbitraryCall(at.get, call) - else computeFinalResult(IdentityFlow) + else computeFinalResult(StringFlowFunctionProperty.identity) case _: IntLikeType => - computeFinalResult(ConstantResultFlow.forVariable(pt, StringTreeDynamicInt)) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + pt, + StringTreeDynamicInt + )) case FloatType | DoubleType => - computeFinalResult(ConstantResultFlow.forVariable(pt, StringTreeDynamicFloat)) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + pt, + StringTreeDynamicFloat + )) case _ => - computeFinalResult(IdentityFlow) + computeFinalResult(StringFlowFunctionProperty.identity) } } } @@ -73,9 +77,12 @@ case class L0VirtualFunctionCallInterpreter() state: InterpretationState ): ProperPropertyComputationResult = { if (at.isDefined) { - computeFinalResult((env: StringTreeEnvironment) => env.update(at.get, env(pt))) + computeFinalResult( + Set(PDUWeb(state.pc, at.get), PDUWeb(state.pc, pt)), + (env: StringTreeEnvironment) => env.update(state.pc, at.get, env(state.pc, pt)) + ) } else { - computeFinalResult(IdentityFlow) + computeFinalResult(StringFlowFunctionProperty.identity) } } @@ -83,7 +90,7 @@ case class L0VirtualFunctionCallInterpreter() * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = - computeFinalResult(StringFlowFunction.lb(target)) + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) } private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { @@ -91,7 +98,7 @@ private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends Assignme protected def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = - computeFinalResult(StringFlowFunction.lb(target)) + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) } /** @@ -107,32 +114,35 @@ private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringI // .head because we want to evaluate only the first argument of append val paramVar = call.params.head.asVar.toPersistentForm(state.tac.stmts) - computeFinalResult((env: StringTreeEnvironment) => { - val valueState = env(paramVar) - - val transformedValueState = paramVar.value.computationalType match { - case ComputationalTypeInt => - if (call.descriptor.parameterType(0).isCharType && valueState.isInstanceOf[StringTreeConst]) { - StringTreeConst(valueState.asInstanceOf[StringTreeConst].string.toInt.toChar.toString) - } else { - valueState - } - case ComputationalTypeFloat | ComputationalTypeDouble => - if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { + computeFinalResult( + Set(PDUWeb(state.pc, paramVar), PDUWeb(state.pc, pt)) ++ at.map(PDUWeb(state.pc, _)), + (env: StringTreeEnvironment) => { + val valueState = env(state.pc, paramVar) + + val transformedValueState = paramVar.value.computationalType match { + case ComputationalTypeInt => + if (call.descriptor.parameterType(0).isCharType && valueState.isInstanceOf[StringTreeConst]) { + StringTreeConst(valueState.asInstanceOf[StringTreeConst].string.toInt.toChar.toString) + } else { + valueState + } + case ComputationalTypeFloat | ComputationalTypeDouble => + if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { + valueState + } else { + StringTreeDynamicFloat + } + case _ => valueState - } else { - StringTreeDynamicFloat - } - case _ => - valueState - } + } - var newEnv = env - if (at.isDefined) { - newEnv = newEnv.update(at.get, transformedValueState) + var newEnv = env + if (at.isDefined) { + newEnv = newEnv.update(state.pc, at.get, transformedValueState) + } + newEnv.update(state.pc, pt, transformedValueState) } - newEnv.update(pt, transformedValueState) - }) + ) } } @@ -147,45 +157,49 @@ private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStri state: InterpretationState ): ProperPropertyComputationResult = { if (at.isEmpty) { - return computeFinalResult(IdentityFlow); + return computeFinalResult(StringFlowFunctionProperty.identity); } val parameterCount = call.params.size parameterCount match { case 1 => call.params.head.asVar.value match { - case intValue: TheIntegerValue => - computeFinalResult((env: StringTreeEnvironment) => { - env(pt) match { - case const: StringTreeConst => - env.update(at.get, StringTreeConst(const.string.substring(intValue.value))) - case _ => - env.update(at.get, StringTreeNode.lb) + case TheIntegerValue(intVal) => + computeFinalResult( + Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), + (env: StringTreeEnvironment) => { + env(state.pc, pt) match { + case StringTreeConst(string) if string.length < intVal => + env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) + case _ => + env.update(state.pc, at.get, StringTreeNode.lb) + } } - }) + ) case _ => - computeFinalResult(StringFlowFunction.noFlow(at.get)) + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) } case 2 => (call.params.head.asVar.value, call.params(1).asVar.value) match { - case (firstIntValue: TheIntegerValue, secondIntValue: TheIntegerValue) => - computeFinalResult((env: StringTreeEnvironment) => { - env(pt) match { - case const: StringTreeConst => - env.update( - at.get, - StringTreeConst(const.string.substring( - firstIntValue.value, - secondIntValue.value - )) - ) - case _ => - env.update(at.get, StringTreeNode.lb) + case (TheIntegerValue(firstIntVal), TheIntegerValue(secondIntVal)) => + computeFinalResult( + Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), + (env: StringTreeEnvironment) => { + env(state.pc, pt) match { + case StringTreeConst(string) if string.length < secondIntVal => + env.update( + state.pc, + at.get, + StringTreeConst(string.substring(firstIntVal, secondIntVal)) + ) + case _ => + env.update(state.pc, at.get, StringTreeNode.lb) + } } - }) + ) case _ => - computeFinalResult(StringFlowFunction.noFlow(at.get)) + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) } case _ => throw new IllegalStateException( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 98a7e309ef..8f90660db2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -8,7 +8,7 @@ package l0 package interpretation import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch @@ -25,7 +25,7 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { instr.name match { // IMPROVE interpret argument for setLength case "setLength" => computeFinalLBFor(instr.receiver.asVar) - case _ => computeFinalResult(IdentityFlow) + case _ => computeFinalResult(StringFlowFunctionProperty.identity) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 87abf46ea4..c474904e7c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -28,8 +28,7 @@ import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis -import org.opalj.tac.fpcf.properties.string.ConstantResultFlow -import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields @@ -73,7 +72,7 @@ case class L1FieldReadInterpreter( } private case class FieldReadState( - var target: PV, + target: PV, var hasWriteInSameMethod: Boolean = false, var hasInit: Boolean = false, var hasUnresolvableAccess: Boolean = false, @@ -115,7 +114,8 @@ case class L1FieldReadInterpreter( if (writeAccesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value - return computeFinalResult(ConstantResultFlow.forVariable( + return computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, target, StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) )) @@ -123,7 +123,7 @@ case class L1FieldReadInterpreter( implicit val accessState: FieldReadState = FieldReadState(target) writeAccesses.foreach { - case (contextId, _, _, parameter) => + case (contextId, pc, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod if (method == state.dm.definedMethod) { @@ -138,7 +138,7 @@ case class L1FieldReadInterpreter( // Field parameter information is not available accessState.hasUnresolvableAccess = true } else { - val entity: SContext = (PUVar(parameter.get._1, parameter.get._2), method) + val entity: SContext = (pc, PUVar(parameter.get._1, parameter.get._2), method) accessState.accessDependees = accessState.accessDependees :+ ps(entity, StringConstancyProperty.key) } } @@ -157,7 +157,7 @@ case class L1FieldReadInterpreter( } else if (accessState.hasDependees) { InterimResult.forUB( InterpretationHandler.getEntity, - StringFlowFunction.ub, + StringFlowFunctionProperty.ub, accessState.dependees.toSet, continuation(accessState, state) ) @@ -174,7 +174,11 @@ case class L1FieldReadInterpreter( trees = trees :+ StringTreeNode.lb } - computeFinalResult(ConstantResultFlow.forVariable(accessState.target, StringTreeNode.reduceMultiple(trees))) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + accessState.target, + StringTreeNode.reduceMultiple(trees) + )) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 99b5717ba9..549dd78570 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -23,7 +23,7 @@ import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualFunction import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualMethodCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0StaticFunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualMethodCallInterpreter -import org.opalj.tac.fpcf.properties.string.IdentityFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc @@ -52,15 +52,16 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) case Assignment(_, target, _: GetField[V]) => StringInterpreter.computeFinalLBFor(target) - case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(IdentityFlow) + case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case stmt @ Assignment(_, targetVar, expr: FieldRead[V]) => + case stmt @ Assignment(_, _, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( stmt, expr ) // Field reads without result usage are irrelevant - case ExprStmt(_, _: FieldRead[V]) => StringInterpreter.computeFinalResult(IdentityFlow) + case ExprStmt(_, _: FieldRead[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) @@ -72,20 +73,22 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ Assignment(_, target, expr: StaticFunctionCall[V]) => + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant - case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(IdentityFlow) + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case stmt @ Assignment(_, target, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) - case _ => StringInterpreter.computeFinalResult(IdentityFlow) + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index f8a297885d..698251cbc1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -25,7 +25,7 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * Processes [[VirtualFunctionCall]]s similar to the [[L0VirtualFunctionCallInterpreter]] but handles arbitrary calls @@ -80,7 +80,7 @@ class L1VirtualFunctionCallInterpreter( depender.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] InterimResult.forUB( InterpretationHandler.getEntity(state), - StringFlowFunction.ub, + StringFlowFunctionProperty.ub, Set(depender.calleeDependee), continuation(state, depender) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index 936b2ae861..70c2403f07 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -24,7 +24,7 @@ package object string { /** * String analysis process a local variable within a particular context, i.e. the method in which it is used. */ - type SContext = (SEntity, Method) + type SContext = (Int, SEntity, Method) /** * The entity used for requesting string constancy information for specific def sites of an entity. The def site diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 94c1dbd2d4..913c60ec80 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -18,36 +18,67 @@ import org.opalj.fpcf.PropertyMetaInformation */ sealed trait StringFlowFunctionPropertyMetaInformation extends PropertyMetaInformation { - final type Self = StringFlowFunction + final type Self = StringFlowFunctionProperty } -trait StringFlowFunction extends (StringTreeEnvironment => StringTreeEnvironment) - with Property +case class StringFlowFunctionProperty private ( + webs: Set[PDUWeb], + flow: StringFlowFunction +) extends Property with StringFlowFunctionPropertyMetaInformation { - final def key: PropertyKey[StringFlowFunction] = StringFlowFunction.key + final def key: PropertyKey[StringFlowFunctionProperty] = StringFlowFunctionProperty.key } -object StringFlowFunction extends StringFlowFunctionPropertyMetaInformation { +object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformation { private final val propertyName = "opalj.StringFlowFunction" - override val key: PropertyKey[StringFlowFunction] = PropertyKey.create(propertyName) + override val key: PropertyKey[StringFlowFunctionProperty] = PropertyKey.create(propertyName) - def ub: StringFlowFunction = ConstantResultFlow.forAll(StringTreeNeutralElement) // TODO should this be the real bottom element? - def ub(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeNeutralElement) - def lb(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeDynamicString) - def noFlow(v: PV): StringFlowFunction = ConstantResultFlow.forVariable(v, StringTreeInvalidElement) -} + def apply(webs: Set[PDUWeb], flow: StringFlowFunction): StringFlowFunctionProperty = { + new StringFlowFunctionProperty( + webs.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => + val index = reducedWebs.indexWhere(_.intersectsWith(web)) + if (index == -1) + reducedWebs :+ web + else + reducedWebs.updated(index, reducedWebs(index).intersect(web)) + }.toSet, + flow + ) + } + + def apply(web: PDUWeb, flow: StringFlowFunction): StringFlowFunctionProperty = + new StringFlowFunctionProperty(Set(web), flow) + + def apply(pc: Int, pv: PV, flow: StringFlowFunction): StringFlowFunctionProperty = + StringFlowFunctionProperty(PDUWeb(pc, pv), flow) + + // TODO should this be the real bottom element? + def ub: StringFlowFunctionProperty = constForAll(StringTreeNeutralElement) + + def identity: StringFlowFunctionProperty = + StringFlowFunctionProperty(Set.empty[PDUWeb], IdentityFlow) -object ConstantResultFlow { - def forAll(result: StringTreeNode): StringFlowFunction = - (env: StringTreeEnvironment) => env.updateAll(result) + def ub(pc: Int, v: PV): StringFlowFunctionProperty = + constForVariableAt(pc, v, StringTreeNeutralElement) - def forVariable(v: PV, result: StringTreeNode): StringFlowFunction = - (env: StringTreeEnvironment) => env.update(v, result) + def lb(pc: Int, v: PV): StringFlowFunctionProperty = + constForVariableAt(pc, v, StringTreeDynamicString) + + def noFlow(pc: Int, v: PV): StringFlowFunctionProperty = + constForVariableAt(pc, v, StringTreeInvalidElement) + + def constForVariableAt(pc: Int, v: PV, result: StringTreeNode): StringFlowFunctionProperty = + StringFlowFunctionProperty(pc, v, (env: StringTreeEnvironment) => env.update(pc, v, result)) + + def constForAll(result: StringTreeNode): StringFlowFunctionProperty = + StringFlowFunctionProperty(Set.empty[PDUWeb], (env: StringTreeEnvironment) => env.updateAll(result)) } +trait StringFlowFunction extends (StringTreeEnvironment => StringTreeEnvironment) + object IdentityFlow extends StringFlowFunction { override def apply(env: StringTreeEnvironment): StringTreeEnvironment = env diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 89ccd359d0..abc01f2fc6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -5,27 +5,34 @@ package fpcf package properties package string -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr /** * @author Maximilian Rüsch */ -case class StringTreeEnvironment(private val map: Map[PV, StringTreeNode]) { +case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { - def apply(v: PV): StringTreeNode = map(v) + private def getWebFor(pc: Int, pv: PV): PDUWeb = map.keys.find(_.containsVarAt(pc, pv)).getOrElse(PDUWeb(pc, pv)) - def update(v: PV, value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.updated(v, value)) + def apply(pc: Int, pv: PV): StringTreeNode = map(getWebFor(pc, pv)) + + def apply(web: PDUWeb): StringTreeNode = map(web) + + def update(web: PDUWeb, value: StringTreeNode): StringTreeEnvironment = + StringTreeEnvironment(map.updated(web, value)) + + def update(pc: Int, pv: PV, value: StringTreeNode): StringTreeEnvironment = + StringTreeEnvironment(map.updated(getWebFor(pc, pv), value)) def updateAll(value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.map { kv => (kv._1, value) }) def join(other: StringTreeEnvironment): StringTreeEnvironment = { - val result = (map.keySet ++ other.map.keys) map { pv => - (map.get(pv), other.map.get(pv)) match { - case (Some(value1), Some(value2)) => pv -> StringTreeOr.fromNodes(value1, value2) - case (Some(value1), None) => pv -> value1 - case (None, Some(value2)) => pv -> value2 + val result = (map.keySet ++ other.map.keys) map { web => + (map.get(web), other.map.get(web)) match { + case (Some(value1), Some(value2)) => web -> StringTreeOr.fromNodes(value1, value2) + case (Some(value1), None) => web -> value1 + case (None, Some(value2)) => web -> value2 case (None, None) => throw new IllegalStateException("Found a key in a map that is not in the map!") } } @@ -33,8 +40,3 @@ case class StringTreeEnvironment(private val map: Map[PV, StringTreeNode]) { StringTreeEnvironment(result.toMap) } } - -object StringTreeEnvironment { - def lb: StringTreeEnvironment = - StringTreeEnvironment(Map.empty[PV, StringTreeNode].withDefaultValue(StringTreeDynamicString)) -} From 7f3586687f0e258418fcbdd49d5122072c8fe514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 20 May 2024 22:54:17 +0200 Subject: [PATCH 428/583] Extend data flow analysis for remaining acyclic regions --- .../org/opalj/fpcf/StringAnalysisTest.scala | 12 +++--- .../flowanalysis/DataFlowAnalysis.scala | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 8 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 ec0e1e7fde..bec640cf27 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 @@ -166,14 +166,14 @@ class L0StringAnalysisTest extends StringAnalysisTest { // Currently broken L0 Tests .filterNot(entity => entity._3.name.startsWith("unknownCharValue")) - it("can be executed without exceptions") { - newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + // it("can be executed without exceptions") { + newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() + as.propertyStore.waitOnPhaseCompletion() + as.propertyStore.shutdown() - validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) - } + validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) + // } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index f4d24733c9..a2c2a9d17f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -42,14 +42,14 @@ object DataFlowAnalysis { case Statement(_) => env case Region(Block, _, entry) => - var currentEnv = pipe(entry, env) + var currentEnv = env var currentNode = limitedFlowGraph.get(entry) while (currentNode.diSuccessors.nonEmpty) { currentEnv = pipe(currentNode.outer, currentEnv) currentNode = currentNode.diSuccessors.head } - currentEnv + pipe(currentNode, currentEnv) case Region(IfThenElse, _, entry) => val entryNode = limitedFlowGraph.get(entry) @@ -60,6 +60,40 @@ object DataFlowAnalysis { envAfterBranches._1.join(envAfterBranches._2) + case Region(IfThen, _, entry) => + val entryNode = limitedFlowGraph.get(entry) + val (yesBranch, noBranch) = if (entryNode.diSuccessors.head.diSuccessors.nonEmpty) { + (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) + } else { + (entryNode.diSuccessors.tail.head, entryNode.diSuccessors.head) + } + + val envAfterEntry = pipe(entry, env) + val envAfterBranches = ( + pipe(yesBranch.diSuccessors.head, pipe(yesBranch, envAfterEntry)), + pipe(noBranch, envAfterEntry) + ) + + envAfterBranches._1.join(envAfterBranches._2) + + case Region(Proper, _, entry) => + val entryNode = limitedFlowGraph.get(entry) + + var currentNodeEnvs = Map((entryNode, pipe(entry, env))) + while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { + val nextNodeEnvs = + currentNodeEnvs.flatMap[(limitedFlowGraph.NodeT, StringTreeEnvironment)] { nodeEnv => + if (nodeEnv._1.diSuccessors.isEmpty) { + Iterable(nodeEnv) + } else { + nodeEnv._1.diSuccessors.map(successor => (successor, pipe(successor, nodeEnv._2))) + } + } + currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } + } + + currentNodeEnvs.valuesIterator.reduce { (env, otherEnv) => env.join(otherEnv) } + case _ => env } } From 85778e7723ca88efdb23b0f13c6d72525bba534e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 19:07:03 +0200 Subject: [PATCH 429/583] Fix substring and append flow functions --- .../properties/string/StringTreeNode.scala | 21 ++++++------------- .../L0VirtualFunctionCallInterpreter.scala | 10 +++++---- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 42da377ee4..e81ff61422 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -6,7 +6,6 @@ package properties package string import scala.collection.immutable.Seq -import scala.collection.mutable import scala.util.Try import scala.util.matching.Regex @@ -111,19 +110,11 @@ case class StringTreeOr(override val children: Seq[StringTreeNode]) extends Stri case orChild: StringTreeOr => newChildren :++= orChild.children case child => newChildren :+= child } - StringTreeOr(removeDuplicates(newChildren)) - } - } - - private def removeDuplicates(someChildren: Seq[StringTreeNode]): Seq[StringTreeNode] = { - val seen = mutable.HashSet.empty[String] - someChildren.flatMap { - case next @ StringTreeConst(string) => - if (!seen.contains(string)) { - seen += string - Some(next) - } else None - case other => Some(other) + val distinctNewChildren = newChildren.distinct + distinctNewChildren.size match { + case 1 => distinctNewChildren.head + case _ => StringTreeOr(distinctNewChildren) + } } } @@ -135,7 +126,7 @@ case class StringTreeOr(override val children: Seq[StringTreeNode]) extends Stri } object StringTreeOr { - def fromNodes(children: StringTreeNode*): StringTreeOr = new StringTreeOr(children) + def fromNodes(children: StringTreeNode*): StringTreeNode = new StringTreeOr(children).simplify } case class StringTreeCond(child: StringTreeNode) extends StringTreeNode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 73b7a1ed6f..78407bb798 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -15,6 +15,7 @@ import org.opalj.br.FloatType import org.opalj.br.IntLikeType import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringTreeConcat import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt @@ -136,11 +137,12 @@ private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringI valueState } + val appendedState = StringTreeConcat.fromNodes(env(state.pc, pt), transformedValueState) var newEnv = env if (at.isDefined) { - newEnv = newEnv.update(state.pc, at.get, transformedValueState) + newEnv = newEnv.update(state.pc, at.get, appendedState) } - newEnv.update(state.pc, pt, transformedValueState) + newEnv.update(state.pc, pt, appendedState) } ) } @@ -169,7 +171,7 @@ private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStri Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), (env: StringTreeEnvironment) => { env(state.pc, pt) match { - case StringTreeConst(string) if string.length < intVal => + case StringTreeConst(string) if string.length <= intVal => env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) case _ => env.update(state.pc, at.get, StringTreeNode.lb) @@ -187,7 +189,7 @@ private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStri Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), (env: StringTreeEnvironment) => { env(state.pc, pt) match { - case StringTreeConst(string) if string.length < secondIntVal => + case StringTreeConst(string) if string.length <= secondIntVal => env.update( state.pc, at.get, From 93bbe9cdfdd13bef1c36ccedf1821d1ed3640cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 19:38:39 +0200 Subject: [PATCH 430/583] Fix computation of registered webs --- .../org/opalj/fpcf/StringAnalysisTest.scala | 3 -- .../src/main/scala/org/opalj/tac/DUWeb.scala | 30 ------------------- .../src/main/scala/org/opalj/tac/PDUWeb.scala | 17 ++++------- .../analyses/string/ComputationState.scala | 8 ++++- .../L0FunctionCallInterpreter.scala | 5 +++- .../string/StringFlowFunction.scala | 17 ++--------- 6 files changed, 18 insertions(+), 62 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala 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 bec640cf27..5fa0248c2e 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 @@ -162,9 +162,6 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities - .filter(entity => entity._3.name.startsWith("simpleStringConcat2")) - // Currently broken L0 Tests - .filterNot(entity => entity._3.name.startsWith("unknownCharValue")) // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala deleted file mode 100644 index 3c3dfe1eb1..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/DUWeb.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac - -import org.opalj.collection.immutable.IntTrieSet -import org.opalj.value.ValueInformation - -case class DUWeb( - defSites: IntTrieSet, - useSites: IntTrieSet -) { - def intersectsWith(other: DUWeb): Boolean = { - other.defSites.intersect(defSites).nonEmpty && other.useSites.intersect(useSites).nonEmpty - } - - def intersect(other: DUWeb): DUWeb = DUWeb(other.defSites.intersect(defSites), other.useSites.intersect(useSites)) - - def toPersistentForm(implicit stmts: Array[Stmt[V]]): PDUWeb = PDUWeb( - defSites.map(pcOfDefSite _), - useSites.map(pcOfDefSite _) - ) -} - -object DUWeb { - def forDVar[Value <: ValueInformation](defSite: Int, dVar: DVar[Value]): DUWeb = - DUWeb(IntTrieSet(defSite), dVar.useSites) - - def forUVar[Value <: ValueInformation](useSite: Int, uVar: UVar[Value]): DUWeb = - DUWeb(uVar.defSites, IntTrieSet(useSite)) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala index 5e9f1d510e..ee38324545 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala @@ -10,22 +10,15 @@ case class PDUWeb( usePCs: IntTrieSet ) { def containsVarAt(pc: Int, pv: PV): Boolean = pv match { - case PDVar(_, varUsePCs) => defPCs.contains(pc) && usePCs.intersect(varUsePCs).nonEmpty - case PUVar(_, varDefPCs) => usePCs.contains(pc) && defPCs.intersect(varDefPCs).nonEmpty + case PDVar(_, _) => defPCs.contains(pc) + case PUVar(_, varDefPCs) => defPCs.intersect(varDefPCs).nonEmpty } - def intersectsWith(other: PDUWeb): Boolean = { - other.defPCs.intersect(defPCs).nonEmpty && other.usePCs.intersect(usePCs).nonEmpty - } - - def intersect(other: PDUWeb): PDUWeb = PDUWeb(other.defPCs.intersect(defPCs), other.usePCs.intersect(usePCs)) + def identifiesSameVarAs(other: PDUWeb): Boolean = other.defPCs.intersect(defPCs).nonEmpty - def size(): Int = defPCs.size + usePCs.size + def combine(other: PDUWeb): PDUWeb = PDUWeb(other.defPCs ++ defPCs, other.usePCs ++ usePCs) - def toValueOriginForm(implicit pcToIndex: Array[Int]): DUWeb = DUWeb( - valueOriginsOfPCs(defPCs, pcToIndex), - valueOriginsOfPCs(usePCs, pcToIndex) - ) + def size: Int = defPCs.size + usePCs.size } object PDUWeb { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 9e8dc26521..73f27cb484 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -58,7 +58,13 @@ case class ComputationState(dm: DefinedMethod, entity: SContext, var tacDependee def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.valuesIterator.flatMap { v => if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs - } + }.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => + val index = reducedWebs.indexWhere(_.identifiesSameVarAs(web)) + if (index == -1) + reducedWebs :+ web + else + reducedWebs.updated(index, reducedWebs(index).combine(web)) + }.iterator } case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index 633bd73a25..2477592636 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -131,7 +131,10 @@ trait L0FunctionCallInterpreter } ) - computeFinalResult(PDUWeb(pc, callState.target), flowFunction)(callState.state) + computeFinalResult( + callState.parameters.map(PDUWeb(pc, _)).toSet + PDUWeb(pc, callState.target), + flowFunction + )(callState.state) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 913c60ec80..f5f24334b2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -21,7 +21,7 @@ sealed trait StringFlowFunctionPropertyMetaInformation extends PropertyMetaInfor final type Self = StringFlowFunctionProperty } -case class StringFlowFunctionProperty private ( +case class StringFlowFunctionProperty( webs: Set[PDUWeb], flow: StringFlowFunction ) extends Property @@ -36,21 +36,8 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat override val key: PropertyKey[StringFlowFunctionProperty] = PropertyKey.create(propertyName) - def apply(webs: Set[PDUWeb], flow: StringFlowFunction): StringFlowFunctionProperty = { - new StringFlowFunctionProperty( - webs.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => - val index = reducedWebs.indexWhere(_.intersectsWith(web)) - if (index == -1) - reducedWebs :+ web - else - reducedWebs.updated(index, reducedWebs(index).intersect(web)) - }.toSet, - flow - ) - } - def apply(web: PDUWeb, flow: StringFlowFunction): StringFlowFunctionProperty = - new StringFlowFunctionProperty(Set(web), flow) + StringFlowFunctionProperty(Set(web), flow) def apply(pc: Int, pv: PV, flow: StringFlowFunction): StringFlowFunctionProperty = StringFlowFunctionProperty(PDUWeb(pc, pv), flow) From f6062dc62b75075367e77ac8da2776dcc7b5fa8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 21:36:43 +0200 Subject: [PATCH 431/583] Return lower bound when cyclic control structures exist --- .../tac/fpcf/analyses/string/StringAnalysis.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index adf2e53ae9..8713f3b6d8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -30,8 +30,10 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.analyses.string.flowanalysis.CyclicRegionType import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph +import org.opalj.tac.fpcf.analyses.string.flowanalysis.Region import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler @@ -123,6 +125,14 @@ trait StringAnalysis extends FPCFAnalysis { } private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { + if (state.controlTree.nodes.exists(n => + n.outer.isInstanceOf[Region] && + n.outer.asInstanceOf[Region].regionType.isInstanceOf[CyclicRegionType] + ) + ) { + return StringConstancyProperty.lb; + } + val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => val defPCs = web.defPCs.toList.sorted if (defPCs.head >= 0) { From a4a903125e05a8e428ad0afebc943ac6df0c20d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 21:36:58 +0200 Subject: [PATCH 432/583] Update many test cases for L0 --- .../string_analysis/l0/L0TestMethods.java | 141 ++++-------------- 1 file changed, 33 insertions(+), 108 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index f97579bc6f..6871227e0e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -205,7 +205,9 @@ public void fromStringArray(int index) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|.*|java.lang.Integer)" + expectedStrings = "(java.lang.Object|.*|java.lang.Integer)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void arrayStaticAndVirtualFunctionCalls(int i) { @@ -276,10 +278,7 @@ public void multipleDefSites(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ac|ab)") }) public void switchRelevantAndIrrelevant(int value) { StringBuilder sb = new StringBuilder("a"); @@ -301,10 +300,7 @@ public void switchRelevantAndIrrelevant(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ac|ab|ad|a)") }) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -329,10 +325,7 @@ public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases and an irrelevant default case", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ac|ab|a)") }) public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -356,10 +349,7 @@ public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and no irrelevant cases and a relevant default case", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c|d)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|ad)") }) public void switchRelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -380,10 +370,7 @@ public void switchRelevantWithRelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d)?|f)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|af|a|ac|ad)") }) public void switchNestedNoNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -411,10 +398,7 @@ public void switchNestedNoNestedDefault(int value, int value2) { @StringDefinitionsCollection( value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|(c|d|e)|f)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|af|ac|ad|ae)") }) public void switchNestedWithNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -445,14 +429,8 @@ public void switchNestedWithNestedDefault(int value, int value2) { @StringDefinitionsCollection( value = "if-else control structure which append to a string builder with an int expr and an int", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(42-42|x)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(^-?\\d+$|x)"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(x|42-42)") }) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb1 = new StringBuilder(); @@ -496,14 +474,8 @@ public void ifElseWithStringBuilderWithFloatExpr() { @StringDefinitionsCollection( value = "if-else control structure which append to a string builder", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(a|b)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b|c)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|b)"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac)") }) public void ifElseWithStringBuilder1() { StringBuilder sb1; @@ -522,12 +494,9 @@ public void ifElseWithStringBuilder1() { } @StringDefinitionsCollection( - value = "if-else control structure which append to a string builder multiple times", + value = "if-else control structure which appends to a string builder multiple times", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(axyz|abcd)") }) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); @@ -547,10 +516,7 @@ public void ifElseWithStringBuilder3() { @StringDefinitionsCollection( value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|abcd|axyz)") }) public void ifElseWithStringBuilder4() { StringBuilder sb = new StringBuilder("a"); @@ -624,10 +590,7 @@ public void ifElseInLoopWithAppendAfterwards() { @StringDefinitionsCollection( value = "if control structure without an else", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab)") }) public void ifWithoutElse() { StringBuilder sb = new StringBuilder("a"); @@ -776,18 +739,9 @@ public void withThrow(String filename) throws IOException { // Multiple string definition values are necessary because the three-address code contains multiple calls to // 'analyzeString' which are currently not filtered out stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:(.*)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:.*|File Content:)"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:.*|File Content:)") }) public void withException(String filename) { StringBuilder sb = new StringBuilder("File Content:"); @@ -803,18 +757,11 @@ public void withException(String filename) { @StringDefinitionsCollection( value = "case with a try-catch-finally exception", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====.*"), + // Exception case without own thrown exception + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====)"), + // Checking the CFG reveals many edges that may lead to this hidden catch node + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(=====.*|==========|=====.*=====|=====)") }) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); @@ -831,18 +778,9 @@ public void tryCatchFinally(String filename) { @StringDefinitionsCollection( value = "case with a try-catch-finally throwable", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:.*"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(BOS::EOS|BOS:.*:EOS)"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(BOS::EOS|BOS:.*:EOS)") }) public void tryCatchFinallyWithThrowable(String filename) { StringBuilder sb = new StringBuilder("BOS:"); @@ -881,9 +819,7 @@ public void simpleClearExamples() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" + expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" ) }) public void advancedClearExampleWithSetLength(int value) { @@ -903,9 +839,7 @@ public void advancedClearExampleWithSetLength(int value) { @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|.*Goodbye)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" + expectedStrings = "(.*Goodbye|init_value:Hello, world!Goodbye)" ) }) public void replaceExamples(int value) { @@ -1002,10 +936,7 @@ public void breakContinueExamples2(int value) { @StringDefinitionsCollection( value = "an example where in the condition of an 'if', a string is appended to a StringBuilder", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime") }) public void ifConditionAppendsToString(String className) { StringBuilder sb = new StringBuilder(); @@ -1122,14 +1053,8 @@ public void finalFieldRead() { @StringDefinitionsCollection( value = "A case with a criss-cross append on two StringBuilders", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Object|ObjectRuntime)"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(RuntimeObject|Runtime)") }) public void crissCrossExample(String className) { StringBuilder sbObj = new StringBuilder("Object"); From 75de3db5d06961b206d21068409929d320c7d9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 21:48:58 +0200 Subject: [PATCH 433/583] Correctly interpret arguments of setLength --- .../L0VirtualMethodCallInterpreter.scala | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 8f90660db2..1fa5860a06 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -7,8 +7,13 @@ package string package l0 package interpretation +import org.opalj.br.fpcf.properties.string.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import org.opalj.value.TheIntegerValue /** * @author Maximilian Rüsch @@ -21,11 +26,39 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { * Currently, this function supports no method calls. However, it treats [[StringBuilder.setLength]] such that it * will return the lower bound for now. */ - override def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { - instr.name match { - // IMPROVE interpret argument for setLength - case "setLength" => computeFinalLBFor(instr.receiver.asVar) - case _ => computeFinalResult(StringFlowFunctionProperty.identity) + override def interpret(call: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { + val pReceiver = call.receiver.asVar.toPersistentForm(state.tac.stmts) + + call.name match { + case "setLength" => + call.params.head.asVar.value match { + case TheIntegerValue(intVal) if intVal == 0 => + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + pReceiver, + StringTreeNeutralElement + )) + + case TheIntegerValue(intVal) => + computeFinalResult( + PDUWeb(state.pc, pReceiver), + (env: StringTreeEnvironment) => { + env(state.pc, pReceiver) match { + case StringTreeConst(string) => + val sb = new StringBuilder(string) + sb.setLength(intVal) + env.update(state.pc, pReceiver, StringTreeConst(sb.toString())) + case _ => + env.update(state.pc, pReceiver, StringTreeNode.lb) + } + } + ) + case _ => + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, pReceiver)) + } + + case _ => + computeFinalResult(StringFlowFunctionProperty.identity) } } } From fc09357ee265b007e473ec0e0357864bc6877e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 21:58:35 +0200 Subject: [PATCH 434/583] Fix interpreting arguments of substring calls --- .../interpretation/L0VirtualFunctionCallInterpreter.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 78407bb798..f4d8da0610 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -171,7 +171,7 @@ private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStri Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), (env: StringTreeEnvironment) => { env(state.pc, pt) match { - case StringTreeConst(string) if string.length <= intVal => + case StringTreeConst(string) if intVal <= string.length => env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) case _ => env.update(state.pc, at.get, StringTreeNode.lb) @@ -189,7 +189,10 @@ private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStri Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), (env: StringTreeEnvironment) => { env(state.pc, pt) match { - case StringTreeConst(string) if string.length <= secondIntVal => + case StringTreeConst(string) + if firstIntVal <= string.length + && secondIntVal <= string.length + && firstIntVal <= secondIntVal => env.update( state.pc, at.get, From 4ad46947acf6bfb9209285102dd8ae98c32e4b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 22:31:02 +0200 Subject: [PATCH 435/583] Return LB for values which were not interpreted --- .../string/l0/interpretation/L0InterpretationHandler.scala | 3 +++ .../string/l1/interpretation/L1InterpretationHandler.scala | 3 +++ 2 files changed, 6 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 4a0e981207..5a596e8148 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -60,6 +60,9 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) + case Assignment(_, target, _) => + StringInterpreter.computeFinalLBFor(target) + case _ => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 549dd78570..82d15737c0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -87,6 +87,9 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) + case Assignment(_, target, _) => + StringInterpreter.computeFinalLBFor(target) + case _ => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } From bdb46285dd8a2b77bab7adf0d137f332f95dc145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 23:40:13 +0200 Subject: [PATCH 436/583] Stabelize traversal for data flow analysis --- .../string_analysis/l0/L0TestMethods.java | 24 ++++++++++------- .../flowanalysis/DataFlowAnalysis.scala | 26 +++++++++++++------ .../string/flowanalysis/FlowGraphNode.scala | 4 ++- .../flowanalysis/StructuralAnalysis.scala | 7 ++--- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 6871227e0e..83145fcba1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -278,7 +278,7 @@ public void multipleDefSites(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ac|ab)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac)") }) public void switchRelevantAndIrrelevant(int value) { StringBuilder sb = new StringBuilder("a"); @@ -300,7 +300,7 @@ public void switchRelevantAndIrrelevant(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ac|ab|ad|a)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac|ad)") }) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -325,7 +325,7 @@ public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases and an irrelevant default case", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ac|ab|a)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac)") }) public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -370,7 +370,7 @@ public void switchRelevantWithRelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|af|a|ac|ad)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|a|af|ac|ad)") }) public void switchNestedNoNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -496,7 +496,7 @@ public void ifElseWithStringBuilder1() { @StringDefinitionsCollection( value = "if-else control structure which appends to a string builder multiple times", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(axyz|abcd)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(abcd|axyz)") }) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); @@ -740,8 +740,8 @@ public void withThrow(String filename) throws IOException { // 'analyzeString' which are currently not filtered out stringDefinitions = { @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:.*|File Content:)"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:.*|File Content:)") + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:|File Content:.*)"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:|File Content:.*)") }) public void withException(String filename) { StringBuilder sb = new StringBuilder("File Content:"); @@ -760,8 +760,12 @@ public void withException(String filename) { @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====.*"), // Exception case without own thrown exception @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====)"), - // Checking the CFG reveals many edges that may lead to this hidden catch node - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(=====.*|==========|=====.*=====|=====)") + // The following cases are detected: + // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append + // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append + // 3. First append succeeds, throws no exception -> only first append + // 4. First append is executed but throws an exception Throwable -> both appends + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(=====|==========|=====.*|=====.*=====)") }) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); @@ -819,7 +823,7 @@ public void simpleClearExamples() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" + expectedStrings = "(Goodbye|init_value:Hello, world!Goodbye)" ) }) public void advancedClearExampleWithSetLength(int value) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index a2c2a9d17f..1620d0b266 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -25,6 +25,10 @@ object DataFlowAnalysis { pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc)(startNode, startEnv) } + /** + * @note This function should be stable with regards to an ordering on the piped flow graph nodes, e.g. a proper + * region should always be traversed in the same way. + */ private def pipeThroughNode( controlTree: ControlTree, superFlowGraph: SuperFlowGraph, @@ -53,7 +57,8 @@ object DataFlowAnalysis { case Region(IfThenElse, _, entry) => val entryNode = limitedFlowGraph.get(entry) - val branches = (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) + val successors = entryNode.diSuccessors.map(_.outer).toList.sorted + val branches = (successors.head, successors.tail.head) val envAfterEntry = pipe(entry, env) val envAfterBranches = (pipe(branches._1, envAfterEntry), pipe(branches._2, envAfterEntry)) @@ -79,20 +84,25 @@ object DataFlowAnalysis { case Region(Proper, _, entry) => val entryNode = limitedFlowGraph.get(entry) + var sortedCurrentNodes = List(entryNode) var currentNodeEnvs = Map((entryNode, pipe(entry, env))) while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { - val nextNodeEnvs = - currentNodeEnvs.flatMap[(limitedFlowGraph.NodeT, StringTreeEnvironment)] { nodeEnv => - if (nodeEnv._1.diSuccessors.isEmpty) { - Iterable(nodeEnv) - } else { - nodeEnv._1.diSuccessors.map(successor => (successor, pipe(successor, nodeEnv._2))) + val nextNodeEnvs = sortedCurrentNodes.flatMap { node => + if (node.diSuccessors.isEmpty) { + Iterable((node, currentNodeEnvs(node))) + } else { + node.diSuccessors.toList.sortBy(_.outer).map { successor => + (successor, pipe(successor, currentNodeEnvs(node))) } } + } + sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } } - currentNodeEnvs.valuesIterator.reduce { (env, otherEnv) => env.join(otherEnv) } + sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => + env.join(currentNodeEnvs(nextNode)) + } case _ => env } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index a46b2ced6c..c71a417e9f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -21,8 +21,10 @@ case object WhileLoop extends CyclicRegionType case object NaturalLoop extends CyclicRegionType case object Improper extends CyclicRegionType -sealed trait FlowGraphNode { +sealed trait FlowGraphNode extends Ordered[FlowGraphNode] { def nodeIds: Set[Int] + + override def compare(that: FlowGraphNode): Int = nodeIds.toList.min.compare(that.nodeIds.toList.min) } case class Region(regionType: RegionType, override val nodeIds: Set[Int], entry: FlowGraphNode) extends FlowGraphNode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 23d499dd36..8a8bc1a997 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -75,9 +75,7 @@ object StructuralAnalysis { (newGraph, newSuperGraph, newRegion) } - PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry)(post.append) { (x, y) => - x.nodeIds.head.compare(y.nodeIds.head) - } + PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry)(post.append) while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) @@ -301,6 +299,9 @@ object StructuralAnalysis { object PostOrderTraversal { + /** + * @note This function should be kept stable with regards to an ordering on the given graph nodes. + */ private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( graph: G, toVisit: Seq[A], From e606db45a940c700daad5098ffeb446d60be2a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 21 May 2024 23:59:12 +0200 Subject: [PATCH 437/583] Fix multiple disjoint def sites that meet at analyzed variable --- .../string_analysis/l0/L0TestMethods.java | 15 ++++----------- .../fpcf/properties/string/StringTreeNode.scala | 2 +- .../tac/fpcf/analyses/string/StringAnalysis.scala | 2 +- .../properties/string/StringTreeEnvironment.scala | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 83145fcba1..c7e822fd00 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -223,12 +223,7 @@ public void arrayStaticAndVirtualFunctionCalls(int i) { @StringDefinitionsCollection( value = "a simple case where multiple definition sites have to be considered", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(java.lang.System|java.lang.Runtime)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.System|java.lang.Runtime)") }) public void multipleConstantDefSites(boolean cond) { String s; @@ -248,7 +243,8 @@ public void multipleConstantDefSites(boolean cond) { expectedLevel = DYNAMIC, expectedStrings = "((java.lang.Object|.*)|.*|java.lang.System|java.lang..*)", realisticLevel = DYNAMIC, - realisticStrings = ".*" + // Array values are currently not interpreted + realisticStrings = "(.*|java.lang.System|java.lang..*)" ) }) public void multipleDefSites(int value) { @@ -1112,10 +1108,7 @@ public void parameterRead2(String stringValue, StringBuilder sbValue) { + "com.oracle.webservices.internal.api.message.BasePropertySet with two " + "definition sites and one usage site", stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(set.*|s.*)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(set.*|s.*)"), }) public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { String name = getName; diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index e81ff61422..8ad0c0b6b1 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -89,7 +89,7 @@ object StringTreeConcat { def fromNodes(children: StringTreeNode*): StringTreeConcat = new StringTreeConcat(children) } -case class StringTreeOr(override val children: Seq[StringTreeNode]) extends StringTreeNode { +case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends StringTreeNode { override def toRegex: String = { children.size match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 8713f3b6d8..17181c1bdb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -153,7 +153,7 @@ trait StringAnalysis extends FPCFAnalysis { state.getFlowFunctionsByPC )(startEnv) - StringConstancyProperty(StringConstancyInformation(resultEnv(state.entity._1, state.entity._2))) + StringConstancyProperty(StringConstancyInformation(resultEnv.mergeAllMatching(state.entity._1, state.entity._2))) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index abc01f2fc6..d5aa1c49e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -13,7 +13,20 @@ import org.opalj.br.fpcf.properties.string.StringTreeOr */ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { - private def getWebFor(pc: Int, pv: PV): PDUWeb = map.keys.find(_.containsVarAt(pc, pv)).getOrElse(PDUWeb(pc, pv)) + private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = map.keys.toList + .filter(_.containsVarAt(pc, pv)) + .sortBy(_.defPCs.toList.min) + + private def getWebFor(pc: Int, pv: PV): PDUWeb = { + val webs = getWebsFor(pc, pv) + webs.size match { + case 0 => PDUWeb(pc, pv) + case 1 => webs.head + case _ => throw new IllegalStateException("Found more than one matching web when only one should be given!") + } + } + + def mergeAllMatching(pc: Int, pv: PV): StringTreeNode = StringTreeOr.fromNodes(getWebsFor(pc, pv).map(map(_)): _*) def apply(pc: Int, pv: PV): StringTreeNode = map(getWebFor(pc, pv)) From 373f3f5133d2583c2aabbde7fcb67ae3e53d9261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 22 May 2024 00:08:25 +0200 Subject: [PATCH 438/583] Fix return value interpretation of virtual function calls --- .../fixtures/string_analysis/l0/L0TestMethods.java | 14 +++----------- .../L0VirtualFunctionCallInterpreter.scala | 13 ++++++------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index c7e822fd00..8e4c38273f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -449,9 +449,7 @@ public void ifElseWithStringBuilderWithIntExpr() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(3.14|^-?\\d*\\.{0,1}\\d+$)2.71828", - realisticLevel = DYNAMIC, - realisticStrings = ".*" + expectedStrings = "(3.142.71828|^-?\\d*\\.{0,1}\\d+$2.71828)" ) }) public void ifElseWithStringBuilderWithFloatExpr() { @@ -968,10 +966,7 @@ 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 = "java.lang.(Object|Runtime)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)") }) public void complexSecondStringBuilderRead(String className) { StringBuilder sbObj = new StringBuilder("Object"); @@ -993,10 +988,7 @@ public void complexSecondStringBuilderRead(String className) { value = "checks if the case, where the value of a StringBuilder depends on the " + "simple construction of a second StringBuilder is determined correctly.", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Object|java.lang.Runtime)") }) public void simpleSecondStringBuilderRead(String className) { StringBuilder sbObj = new StringBuilder("Object"); diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index f4d8da0610..c315be75e9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -53,19 +53,18 @@ case class L0VirtualFunctionCallInterpreter() interpretSubstringCall(at, pt, call) case _ => call.descriptor.returnType match { - case obj: ObjectType if obj == ObjectType.String => - if (at.isDefined) interpretArbitraryCall(at.get, call) - else computeFinalResult(StringFlowFunctionProperty.identity) - case _: IntLikeType => + case obj: ObjectType if obj == ObjectType.String && at.isDefined => + interpretArbitraryCall(at.get, call) + case _: IntLikeType if at.isDefined => computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, - pt, + at.get, StringTreeDynamicInt )) - case FloatType | DoubleType => + case FloatType | DoubleType if at.isDefined => computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, - pt, + at.get, StringTreeDynamicFloat )) case _ => From e74bf877f609d756c2ef625db9de978e3f25865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 25 May 2024 13:42:30 +0200 Subject: [PATCH 439/583] Correctly forward string builder references during append calls --- .../fpcf/properties/string/StringTreeNode.scala | 2 +- .../fpcf/analyses/string/ComputationState.scala | 4 ++-- .../L0VirtualFunctionCallInterpreter.scala | 16 ++++++++-------- .../string/StringTreeEnvironment.scala | 17 +++++++++++++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 8ad0c0b6b1..f3e6ea1397 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -53,7 +53,7 @@ case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { StringTreeRepetition(simplifiedChild) } - override def constancyLevel: StringConstancyLevel.Value = child.constancyLevel + override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = StringTreeRepetition(child.replaceParameters(parameters)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 73f27cb484..d75b7461d1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -55,10 +55,10 @@ case class ComputationState(dm: DefinedMethod, entity: SContext, var tacDependee ) }.toMap - def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.valuesIterator.flatMap { v => + def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.values.flatMap { v => if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs - }.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => + }.toSeq.sortBy(_.defPCs.toList.min).foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => val index = reducedWebs.indexWhere(_.identifiesSameVarAs(web)) if (index == -1) reducedWebs :+ web diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index c315be75e9..99d5ead4ba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -89,8 +89,10 @@ case class L0VirtualFunctionCallInterpreter() /** * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. */ - private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = + private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = { + // Improve: Support fluent API by returning combined web for both assignment target and call target computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + } } private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { @@ -114,8 +116,11 @@ private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringI // .head because we want to evaluate only the first argument of append val paramVar = call.params.head.asVar.toPersistentForm(state.tac.stmts) + val ptWeb = PDUWeb(state.pc, pt) + val combinedWeb = if (at.isDefined) ptWeb.combine(PDUWeb(state.pc, at.get)) else ptWeb + computeFinalResult( - Set(PDUWeb(state.pc, paramVar), PDUWeb(state.pc, pt)) ++ at.map(PDUWeb(state.pc, _)), + Set(PDUWeb(state.pc, paramVar), combinedWeb), (env: StringTreeEnvironment) => { val valueState = env(state.pc, paramVar) @@ -136,12 +141,7 @@ private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringI valueState } - val appendedState = StringTreeConcat.fromNodes(env(state.pc, pt), transformedValueState) - var newEnv = env - if (at.isDefined) { - newEnv = newEnv.update(state.pc, at.get, appendedState) - } - newEnv.update(state.pc, pt, appendedState) + env.update(combinedWeb, StringTreeConcat.fromNodes(env(state.pc, pt), transformedValueState)) } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index d5aa1c49e2..51b68e473f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -17,6 +17,10 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { .filter(_.containsVarAt(pc, pv)) .sortBy(_.defPCs.toList.min) + private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys.toList + .filter(_.identifiesSameVarAs(web)) + .sortBy(_.defPCs.toList.min) + private def getWebFor(pc: Int, pv: PV): PDUWeb = { val webs = getWebsFor(pc, pv) webs.size match { @@ -26,14 +30,23 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { } } + private def getWebFor(web: PDUWeb): PDUWeb = { + val webs = getWebsFor(web) + webs.size match { + case 0 => web + case 1 => webs.head + case _ => throw new IllegalStateException("Found more than one matching web when only one should be given!") + } + } + def mergeAllMatching(pc: Int, pv: PV): StringTreeNode = StringTreeOr.fromNodes(getWebsFor(pc, pv).map(map(_)): _*) def apply(pc: Int, pv: PV): StringTreeNode = map(getWebFor(pc, pv)) - def apply(web: PDUWeb): StringTreeNode = map(web) + def apply(web: PDUWeb): StringTreeNode = map(getWebFor(web)) def update(web: PDUWeb, value: StringTreeNode): StringTreeEnvironment = - StringTreeEnvironment(map.updated(web, value)) + StringTreeEnvironment(map.updated(getWebFor(web), value)) def update(pc: Int, pv: PV, value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.updated(getWebFor(pc, pv), value)) From 013e883c219d01615853a6fd7e106ab7d77346b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 25 May 2024 14:44:10 +0200 Subject: [PATCH 440/583] Refactor method string flow into its own property --- .../info/StringAnalysisReflectiveCalls.scala | 5 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 10 +- .../analyses/string/ComputationState.scala | 14 +- .../string/MethodStringFlowAnalysis.scala | 146 +++++++++++ .../fpcf/analyses/string/StringAnalysis.scala | 244 ++---------------- .../string/StringAnalysisScheduler.scala | 114 ++++++++ .../InterpretationHandler.scala | 41 +++ .../analyses/string/l0/L0StringAnalysis.scala | 11 +- .../analyses/string/l1/L1StringAnalysis.scala | 11 +- .../L1FieldReadInterpreter.scala | 2 +- .../tac/fpcf/analyses/string/package.scala | 6 - .../properties/string/MethodStringFlow.scala | 49 ++++ 12 files changed, 407 insertions(+), 246 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.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 index 3255a3b0f5..780acebf4a 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 @@ -308,8 +308,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) - implicit val (propertyStore, analyses) = manager.runAll( - if (runIntraproceduralAnalysis) LazyL0StringAnalysis else LazyL1StringAnalysis + implicit val (propertyStore, _) = manager.runAll( + if (runIntraproceduralAnalysis) LazyL0StringAnalysis.allRequiredAnalyses + else LazyL1StringAnalysis.allRequiredAnalyses ) // Stores the obtained results for each supported reflective operation 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 5fa0248c2e..a6586db217 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 @@ -22,9 +22,7 @@ import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.SEntity import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis -import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis -import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringFlowAnalysis sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -138,7 +136,7 @@ object StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l0.L0StringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch @@ -158,7 +156,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { } describe("the org.opalj.fpcf.L0StringAnalysis is started") { - val as = executeAnalyses(LazyL0StringAnalysis, LazyL0StringFlowAnalysis) + val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities @@ -175,7 +173,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { } /** - * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis]] works correctly with + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis]] works correctly with * respect to some well-defined tests. * * @author Maximilian Rüsch @@ -194,7 +192,7 @@ class L1StringAnalysisTest extends StringAnalysisTest { } describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses(LazyL1StringAnalysis, LazyL1StringFlowAnalysis) + val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) // L0 Tests diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index d75b7461d1..7d8fbb8607 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -15,6 +15,7 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -23,7 +24,18 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * 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(dm: DefinedMethod, entity: SContext, var tacDependee: EOptionP[Method, TACAI]) { +case class StringAnalysisState(entity: SContext, var stringFlowDependee: EOptionP[Method, MethodStringFlow]) { + + def hasDependees: Boolean = stringFlowDependee.isRefinable + def dependees: Set[EOptionP[Method, MethodStringFlow]] = Set(stringFlowDependee) +} + +/** + * 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(entity: Method, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { def tac: TAC = { if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala new file mode 100644 index 0000000000..f5ad2ad1e7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -0,0 +1,146 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string + +import org.opalj.ai.ImmediateVMExceptionsOriginOffset +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeParameter +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimEUB +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.tac.fpcf.analyses.string.flowanalysis.CyclicRegionType +import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph +import org.opalj.tac.fpcf.analyses.string.flowanalysis.Region +import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement +import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.MethodStringFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment + +/** + * @author Maximilian Rüsch + */ +class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis { + + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + + def analyze(method: Method): ProperPropertyComputationResult = { + val state = ComputationState(method, declaredMethods(method), ps(method, TACAI.key)) + + if (state.tacDependee.isRefinable) { + InterimResult.forUB( + state.entity, + MethodStringFlow.ub, + Set(state.tacDependee), + continuation(state) + ) + } else if (state.tacDependee.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + Result(state.entity, MethodStringFlow.lb) + } else { + determinePossibleStrings(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(implicit state: ComputationState): ProperPropertyComputationResult = { + implicit val tac: TAC = state.tac + + state.flowGraph = FlowGraph(tac.cfg) + val (_, superFlowGraph, controlTree) = + StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) + state.superFlowGraph = superFlowGraph + state.controlTree = controlTree + + state.flowGraph.nodes.toOuter.foreach { + case Statement(pc) if pc >= 0 => + state.updateDependee(pc, propertyStore(MethodPC(pc, state.dm), StringFlowFunctionProperty.key)) + + case _ => + } + + computeResults + } + + private def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case FinalP(_: TACAI) if eps.pk.equals(TACAI.key) => + state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] + determinePossibleStrings(state) + + case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunctionProperty.key) => + state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunctionProperty]]) + computeResults(state) + + case _ => + getInterimResult(state) + } + } + + private def computeResults(implicit state: ComputationState): ProperPropertyComputationResult = { + if (state.hasDependees) { + getInterimResult(state) + } else { + Result(state.entity, computeNewUpperBound(state)) + } + } + + private def getInterimResult(state: ComputationState): InterimResult[MethodStringFlow] = { + InterimResult.forUB( + state.entity, + computeNewUpperBound(state), + state.dependees.toSet, + continuation(state) + ) + } + + private def computeNewUpperBound(state: ComputationState): MethodStringFlow = { + if (state.controlTree.nodes.exists(n => + n.outer.isInstanceOf[Region] && + n.outer.asInstanceOf[Region].regionType.isInstanceOf[CyclicRegionType] + ) + ) { + return MethodStringFlow.lb; + } + + val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => + val defPCs = web.defPCs.toList.sorted + if (defPCs.head >= 0) { + (web, StringTreeNeutralElement) + } else { + val pc = defPCs.head + if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { + (web, StringTreeDynamicString) + } else { + (web, StringTreeParameter.forParameterPC(pc)) + } + } + }.toMap) + + MethodStringFlow(DataFlowAnalysis.compute( + state.controlTree, + state.superFlowGraph, + state.getFlowFunctionsByPC + )(startEnv)) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 17181c1bdb..6cbe0633f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -5,258 +5,58 @@ package fpcf package analyses package string -import org.opalj.ai.ImmediateVMExceptionsOriginOffset -import org.opalj.br.FieldType import org.opalj.br.Method -import org.opalj.br.analyses.DeclaredMethods -import org.opalj.br.analyses.DeclaredMethodsKey -import org.opalj.br.analyses.ProjectInformationKeys 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.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement -import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimEUB +import org.opalj.fpcf.EPK import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyBounds -import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.string.flowanalysis.CyclicRegionType -import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis -import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph -import org.opalj.tac.fpcf.analyses.string.flowanalysis.Region -import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement -import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty -import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.properties.string.MethodStringFlow /** * @author Maximilian Rüsch */ -trait StringAnalysis extends FPCFAnalysis { - - val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) +class StringAnalysis(override val project: SomeProject) extends FPCFAnalysis { def analyze(data: SContext): ProperPropertyComputationResult = { - val state = ComputationState(declaredMethods(data._3), data, ps(data._3, TACAI.key)) - - if (state.tacDependee.isRefinable) { - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees.toSet, - continuation(state) - ) - } else if (state.tacDependee.ub.tac.isEmpty) { - // No TAC available, e.g., because the method has no body - Result(state.entity, StringConstancyProperty.lb) - } else { - determinePossibleStrings(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(implicit state: ComputationState): ProperPropertyComputationResult = { - implicit val tac: TAC = state.tac - - state.flowGraph = FlowGraph(tac.cfg) - val (_, superFlowGraph, controlTree) = - StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) - state.superFlowGraph = superFlowGraph - state.controlTree = controlTree - - state.flowGraph.nodes.toOuter.foreach { - case Statement(pc) if pc >= 0 => - state.updateDependee(pc, propertyStore(MethodPC(pc, state.dm), StringFlowFunctionProperty.key)) - - case _ => - } - - computeResults + computeResults(StringAnalysisState(data, ps(data._3, MethodStringFlow.key))) } - private def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(state: StringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case FinalP(_: TACAI) if eps.pk.equals(TACAI.key) => - state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] - determinePossibleStrings(state) - - case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunctionProperty.key) => - state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunctionProperty]]) + case _ if eps.pk.equals(MethodStringFlow.key) => + state.stringFlowDependee = eps.asInstanceOf[EOptionP[Method, MethodStringFlow]] computeResults(state) case _ => - getInterimResult(state) + throw new IllegalArgumentException(s"Unexpected eps in continuation: $eps") } } - private def computeResults(implicit state: ComputationState): ProperPropertyComputationResult = { + private def computeResults(implicit state: StringAnalysisState): ProperPropertyComputationResult = { if (state.hasDependees) { - getInterimResult(state) - } else { - Result(state.entity, computeNewUpperBound(state)) - } - } - - private def getInterimResult(state: ComputationState): InterimResult[StringConstancyProperty] = { - InterimResult( - state.entity, - StringConstancyProperty.lb, - computeNewUpperBound(state), - state.dependees.toSet, - continuation(state) - ) - } - - private def computeNewUpperBound(state: ComputationState): StringConstancyProperty = { - if (state.controlTree.nodes.exists(n => - n.outer.isInstanceOf[Region] && - n.outer.asInstanceOf[Region].regionType.isInstanceOf[CyclicRegionType] + InterimResult( + state.entity, + StringConstancyProperty.lb, + computeNewUpperBound(state), + state.dependees.toSet, + continuation(state) ) - ) { - return StringConstancyProperty.lb; - } - - val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => - val defPCs = web.defPCs.toList.sorted - if (defPCs.head >= 0) { - (web, StringTreeNeutralElement) - } else { - val pc = defPCs.head - if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - (web, StringTreeDynamicString) - } else { - (web, StringTreeParameter.forParameterPC(pc)) - } - } - }.toMap) - - val resultEnv = DataFlowAnalysis.compute( - state.controlTree, - state.superFlowGraph, - state.getFlowFunctionsByPC - )(startEnv) - - StringConstancyProperty(StringConstancyInformation(resultEnv.mergeAllMatching(state.entity._1, state.entity._2))) - } -} - -object StringAnalysis { - - /** - * This function checks whether a given type is a supported primitive type. Supported currently - * means short, int, float, or double. - */ - private 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 - * `typeName` may be approximated by the string analysis better than just the lower bound. - * - * @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" || isSupportedPrimitiveNumberType(typeName) || - typeName == "java.lang.String" || typeName == "java.lang.String[]" - - /** - * Determines whether a given element is supported by the string analysis. - * - * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, - * see [[StringAnalysis.isSupportedType(String)]]. - */ - def isSupportedType(v: V): Boolean = - if (v.value.isPrimitiveValue) { - isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) } else { - try { - isSupportedType(v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava) - } catch { - case _: Exception => false - } + Result(state.entity, computeNewUpperBound(state)) } - - def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) -} - -sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) - - override def uses: Set[PropertyBounds] = Set( - PropertyBounds.ub(TACAI), - PropertyBounds.ub(StringFlowFunctionProperty) - ) - - override final type InitializationData = StringAnalysis - - 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 = {} -} - -trait LazyStringAnalysis - extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(StringConstancyProperty.key, initData.analyze) - - initData } - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) -} - -sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunctionProperty) - - override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) - - override final type InitializationData = InterpretationHandler - - 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 = {} -} - -trait LazyStringFlowAnalysis - extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(StringFlowFunctionProperty.key, initData.analyze) - - initData + private def computeNewUpperBound(state: StringAnalysisState): StringConstancyProperty = { + StringConstancyProperty(state.stringFlowDependee match { + case UBP(methodStringFlow) => StringConstancyInformation(methodStringFlow(state.entity._1, state.entity._2)) + case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub + }) } - - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala new file mode 100644 index 0000000000..e48471d366 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala @@ -0,0 +1,114 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string + +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +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.properties.string.StringConstancyProperty +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.MethodStringFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * @author Maximilian Rüsch + */ +sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) + + override def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(MethodStringFlow)) + + override final type InitializationData = StringAnalysis + + override def init(p: SomeProject, ps: PropertyStore): InitializationData = new StringAnalysis(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 = {} +} + +object LazyStringAnalysis + extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, analysis: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) + analysis + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq.empty +} + +sealed trait MethodStringFlowAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.ub(MethodStringFlow) + + override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.ub(StringFlowFunctionProperty) + ) + + override final type InitializationData = MethodStringFlowAnalysis + override def init(p: SomeProject, ps: PropertyStore): InitializationData = new MethodStringFlowAnalysis(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 = {} +} + +object LazyMethodStringFlowAnalysis + extends MethodStringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(MethodStringFlow.key, initData.analyze) + initData + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} + +sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunctionProperty) + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) + + override final type InitializationData = InterpretationHandler + + 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 = {} +} + +trait LazyStringFlowAnalysis + extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(StringFlowFunctionProperty.key, initData.analyze) + + initData + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 447908d3e4..837d0c4f64 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -7,6 +7,7 @@ package string package interpretation import org.opalj.br.DefinedMethod +import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.string.StringTreeNode @@ -74,4 +75,44 @@ object InterpretationHandler { def getEntity(pc: Int)(implicit state: InterpretationState): MethodPC = getEntity(pc, state.dm) def getEntity(pc: Int, dm: DefinedMethod): MethodPC = MethodPC(pc, dm) + + /** + * This function checks whether a given type is a supported primitive type. Supported currently + * means short, int, float, or double. + */ + private 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 + * `typeName` may be approximated by the string analysis better than just the lower bound. + * + * @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" || isSupportedPrimitiveNumberType(typeName) || + typeName == "java.lang.String" || typeName == "java.lang.String[]" + + /** + * Determines whether a given element is supported by the string analysis. + * + * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, + * see [[InterpretationHandler.isSupportedType(String)]]. + */ + def isSupportedType(v: V): Boolean = + if (v.value.isPrimitiveValue) { + isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) + } else { + try { + isSupportedType(v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava) + } catch { + case _: Exception => false + } + } + + def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala index ae94eb218c..e60951dece 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala @@ -7,17 +7,20 @@ package string package l0 import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0InterpretationHandler /** * @author Maximilian Rüsch */ -class L0StringAnalysis(override val project: SomeProject) extends StringAnalysis +object LazyL0StringAnalysis { -object LazyL0StringAnalysis extends LazyStringAnalysis { - - override def init(p: SomeProject, ps: PropertyStore): InitializationData = new L0StringAnalysis(p) + def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( + LazyStringAnalysis, + LazyMethodStringFlowAnalysis, + LazyL0StringFlowAnalysis + ) } object LazyL0StringFlowAnalysis extends LazyStringFlowAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 51dd3d25e9..c2bee6754a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -8,6 +8,7 @@ package l1 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore @@ -16,8 +17,6 @@ import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHand /** * @author Maximilian Rüsch */ -class L1StringAnalysis(val project: SomeProject) extends StringAnalysis - object L1StringAnalysis { private[l1] final val FieldWriteThresholdConfigKey = { @@ -25,9 +24,13 @@ object L1StringAnalysis { } } -object LazyL1StringAnalysis extends LazyStringAnalysis { +object LazyL1StringAnalysis { - override final def init(p: SomeProject, ps: PropertyStore): InitializationData = new L1StringAnalysis(p) + def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( + LazyStringAnalysis, + LazyMethodStringFlowAnalysis, + LazyL1StringFlowAnalysis + ) } object LazyL1StringFlowAnalysis extends LazyStringFlowAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index c474904e7c..0e7578c079 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -101,7 +101,7 @@ case class L1FieldReadInterpreter( override def interpretExpr(target: PV, fieldRead: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - if (!StringAnalysis.isSupportedType(fieldRead.declaredFieldType)) { + if (!InterpretationHandler.isSupportedType(fieldRead.declaredFieldType)) { return computeFinalLBFor(target) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index 70c2403f07..edcf5ce1e4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -26,11 +26,5 @@ package object string { */ type SContext = (Int, SEntity, Method) - /** - * The entity used for requesting string constancy information for specific def sites of an entity. The def site - * should be given as a [[org.opalj.br.PC]]. - */ - case class DUSiteEntity(pc: Int, dm: DefinedMethod, tac: TAC, entity: SEntity) - case class MethodPC(pc: Int, dm: DefinedMethod) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala new file mode 100644 index 0000000000..103a8b2cb1 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala @@ -0,0 +1,49 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package properties +package string + +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey +import org.opalj.fpcf.PropertyMetaInformation + +/** + * @author Maximilian Rüsch + */ +sealed trait MethodStringFlowPropertyMetaInformation extends PropertyMetaInformation { + + final type Self = MethodStringFlow +} + +case class MethodStringFlow(private val env: StringTreeEnvironment) extends Property + with MethodStringFlowPropertyMetaInformation { + + final def key: PropertyKey[MethodStringFlow] = MethodStringFlow.key + + def apply(pc: Int, pv: PV): StringTreeNode = env.mergeAllMatching(pc, pv) +} + +object MethodStringFlow extends MethodStringFlowPropertyMetaInformation { + + private final val propertyName = "opalj.MethodStringFlow" + + override val key: PropertyKey[MethodStringFlow] = PropertyKey.create(propertyName) + + def ub: MethodStringFlow = AllNeutralMethodStringFlow + def lb: MethodStringFlow = AllDynamicMethodStringFlow +} + +object AllNeutralMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { + + override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeNeutralElement +} + +object AllDynamicMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { + + override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeDynamicString +} From 76f430211fa136efbf3ad070c6e47b6a7e7deba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 25 May 2024 21:12:05 +0200 Subject: [PATCH 441/583] Fix some of the L1 test cases --- .../string_analysis/l0/L0TestMethods.java | 15 +++++ .../string_analysis/l1/L1TestMethods.java | 67 +++++-------------- .../org/opalj/fpcf/StringAnalysisTest.scala | 19 ++---- .../L1InterpretationHandler.scala | 6 +- 4 files changed, 40 insertions(+), 67 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 8e4c38273f..015ff2050c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -235,6 +235,21 @@ public void multipleConstantDefSites(boolean cond) { analyzeString(s); } + @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()); + } + @StringDefinitionsCollection( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index abd2be0978..1d8cf48c85 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -60,10 +60,7 @@ public L1TestMethods(float e) { @StringDefinitionsCollection( value = "a case where a very simple non-virtual function call is interpreted", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime") }) public void simpleNonVirtualFunctionCallTest(int i) { analyzeString(getRuntimeClassName()); @@ -72,12 +69,7 @@ public void simpleNonVirtualFunctionCallTest(int i) { @StringDefinitionsCollection( value = "a case where a non-virtual function call inside an if statement is interpreted", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)") }) public void simpleNonVirtualFunctionCallTestWithIf(int i) { String s; @@ -92,15 +84,9 @@ public void simpleNonVirtualFunctionCallTestWithIf(int i) { } @StringDefinitionsCollection( - value = "a case where the initialization of a StringBuilder depends on > 1 non-virtual " - + "function calls and a constant", + 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)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)") }) public void initFromNonVirtualFunctionCallTest(int i) { String s; @@ -190,31 +176,10 @@ 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)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - public void appendWithTwoDefSites(int i) { - String s; - if (i > 0) { - s = "great"; - } else { - s = "not great"; - } - 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)", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(It is great|It is Hello, World)") }) public void appendWithTwoDefSitesWithFuncCallTest(int i) { String s; @@ -337,7 +302,10 @@ private static String tieNameForCompiler(String var0) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(another value|some value|^null$)" + expectedStrings = "(another value|some value|^null$)", + // Contains a field write in the same method which cannot be captured by flow functions + realisticLevel = DYNAMIC, + realisticStrings = ".*" ) }) public void fieldReadTest() { @@ -482,14 +450,8 @@ private static String severalReturnValuesStaticFunction(int i) { @StringDefinitionsCollection( value = "a case where a non-virtual and a static function have no return values at all", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = ".*" - ), - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = ".*" - ) + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public void functionWithNoReturnValueTest1() { analyzeString(noReturnFunction1()); @@ -522,7 +484,12 @@ public void getStaticFieldTest() { @StringDefinitionsCollection( value = "a case where a String array field is read", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(January|February|March|April)") + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(January|February|March|April)", + realisticLevel = DYNAMIC, + realisticStrings = ".*" + ) }) public void getStringArrayField(int i) { analyzeString(monthNames[i]); 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 a6586db217..bd0f05e419 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 @@ -183,6 +183,11 @@ class L1StringAnalysisTest extends StringAnalysisTest { override def level = 1 override def init(p: Project[URL]): Unit = { + val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(domain) + case Some(requirements) => requirements + domain + } p.updateProjectInformationKeyInitializationData(FieldAccessInformationKey) { case None => Seq(EagerFieldAccessInformationAnalysis) case Some(requirements) => requirements :+ EagerFieldAccessInformationAnalysis @@ -195,24 +200,10 @@ class L1StringAnalysisTest extends StringAnalysisTest { val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) - // L0 Tests - .filterNot(entity => entity._3.name == "simpleStringConcat") // Waits on string_concat and "substring" - .filterNot(entity => entity._3.name.startsWith("fromConstantAndFunctionCall")) // Waits on string_concat and "substring" - // Currently broken L0 Tests - .filterNot(entity => entity._3.name.startsWith("unknownCharValue")) - // L1 Tests - .filterNot(entity => entity._3.name.startsWith("getStaticTest")) // Waits on string_concat and "substring" - .filterNot(entity => entity._3.name.startsWith("functionWithFunctionParameter")) // Waits on string_concat and "substring" - .filterNot(entity => entity._3.name.startsWith("knownHierarchyInstanceTest")) // Waits on string_concat and "substring" // Currently broken L1 Tests .filterNot(entity => entity._3.name.startsWith("cyclicDependencyTest")) .filterNot(entity => entity._3.name.startsWith("unknownHierarchyInstanceTest")) - .filterNot(entity => entity._3.name.startsWith("severalReturnValuesTest1")) - .filterNot(entity => entity._3.name.startsWith("severalReturnValuesTest2")) .filterNot(entity => entity._3.name.startsWith("crissCrossExample")) - .filterNot(entity => entity._3.name.startsWith("directAppendConcatsWith2ndStringBuilder")) - .filterNot(entity => entity._3.name.startsWith("parameterRead")) - .filterNot(entity => entity._3.name.startsWith("fromStringArray")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 82d15737c0..1d6720ae10 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -50,9 +50,6 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) - case Assignment(_, target, _: GetField[V]) => StringInterpreter.computeFinalLBFor(target) - - case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case stmt @ Assignment(_, _, expr: FieldRead[V]) => L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( @@ -87,6 +84,9 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case Assignment(_, target, _) => StringInterpreter.computeFinalLBFor(target) From ca87162eed7370dc45e44564ed9899221042265e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 25 May 2024 21:25:07 +0200 Subject: [PATCH 442/583] Simplify interface of interpretation handler levels --- .../InterpretationHandler.scala | 17 ++- .../L0InterpretationHandler.scala | 65 +++++------ .../L1InterpretationHandler.scala | 105 ++++++++---------- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 837d0c4f64..f59372d17e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -44,7 +44,7 @@ abstract class InterpretationHandler extends FPCFAnalysis { // No TAC available, e.g., because the method has no body StringInterpreter.computeFinalResult(StringFlowFunctionProperty.constForAll(StringTreeNode.lb)) } else { - processNew + processStatementForState } } @@ -53,7 +53,7 @@ abstract class InterpretationHandler extends FPCFAnalysis { case finalEP: FinalEP[_, _] if eps.pk.equals(TACAI.key) => state.tacDependee = finalEP.asInstanceOf[FinalEP[Method, TACAI]] - processNew(state) + processStatementForState(state) case _ => InterimResult.forUB( @@ -65,7 +65,18 @@ abstract class InterpretationHandler extends FPCFAnalysis { } } - protected def processNew(implicit state: InterpretationState): ProperPropertyComputationResult + private def processStatementForState(implicit state: InterpretationState): ProperPropertyComputationResult = { + val defSiteOpt = valueOriginOfPC(state.pc, state.tac.pcToIndex); + if (defSiteOpt.isEmpty) { + throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: ${state.pc}") + } + + processStatement(state)(state.tac.stmts(defSiteOpt.get)) + } + + protected def processStatement( + implicit state: InterpretationState + ): PartialFunction[Stmt[V], ProperPropertyComputationResult] } object InterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 5a596e8148..2a4445ca3d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -21,51 +21,44 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class L0InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { - override protected def processNew(implicit + override protected def processStatement(implicit state: InterpretationState - ): ProperPropertyComputationResult = { - val duSiteOpt = valueOriginOfPC(state.pc, state.tac.pcToIndex); - if (duSiteOpt.isEmpty) { - throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: ${state.pc}") - } + ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) - state.tac.stmts(duSiteOpt.get) match { - case stmt @ Assignment(_, _, expr: SimpleValueConst) => - SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + // Currently unsupported + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.computeFinalLBFor(target) - // Currently unsupported - case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) - case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) - // Static function calls without return value usage are irrelevant - case ExprStmt(_, _: StaticFunctionCall[V]) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) - case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) + case Assignment(_, target, _) => + StringInterpreter.computeFinalLBFor(target) - case Assignment(_, target, _) => - StringInterpreter.computeFinalLBFor(target) - - case _ => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - } + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 1d6720ae10..4a6aa5b1b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -36,63 +36,56 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processNew(implicit + override protected def processStatement(implicit state: InterpretationState - ): ProperPropertyComputationResult = { - val defSiteOpt = valueOriginOfPC(state.pc, state.tac.pcToIndex); - if (defSiteOpt.isEmpty) { - throw new IllegalArgumentException(s"Obtained a pc that does not represent a definition site: ${state.pc}") - } - - state.tac.stmts(defSiteOpt.get) match { - case stmt @ Assignment(_, _, expr: SimpleValueConst) => - SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) - - // Currently unsupported - case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) - - case stmt @ Assignment(_, _, expr: FieldRead[V]) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( - stmt, - expr - ) - // Field reads without result usage are irrelevant - case ExprStmt(_, _: FieldRead[V]) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - - case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => - new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - - case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - - case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) - // Static function calls without return value usage are irrelevant - case ExprStmt(_, _: StaticFunctionCall[V]) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - - // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) - - case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter.interpret(vmc) - case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter.interpret(nvmc) - - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - - case Assignment(_, target, _) => - StringInterpreter.computeFinalLBFor(target) - - case _ => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - } + ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + + // Currently unsupported + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) + + case stmt @ Assignment(_, _, expr: FieldRead[V]) => + L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( + stmt, + expr + ) + // Field reads without result usage are irrelevant + case ExprStmt(_, _: FieldRead[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => + L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + + case vmc: VirtualMethodCall[V] => + L0VirtualMethodCallInterpreter.interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => + L0NonVirtualMethodCallInterpreter.interpret(nvmc) + + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case Assignment(_, target, _) => + StringInterpreter.computeFinalLBFor(target) + + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } } From dc18adb4f10f101cbca585b1127fbaeb615551b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 25 May 2024 22:36:59 +0200 Subject: [PATCH 443/583] Let loops map to dynamic elements if they modify the environment --- .../string_analysis/l0/L0TestMethods.java | 10 +-- .../properties/string/StringTreeNode.scala | 36 +---------- .../string/MethodStringFlowAnalysis.scala | 10 --- .../flowanalysis/DataFlowAnalysis.scala | 61 +++++++++++++++++++ 4 files changed, 69 insertions(+), 48 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 015ff2050c..4c0e688777 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -579,7 +579,7 @@ public void simpleForLoopWithKnownBounds() { expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "((x|^-?\\d+$))*yz", realisticLevel = DYNAMIC, - realisticStrings = ".*" + realisticStrings = "(.*|.*yz)" ) }) public void ifElseInLoopWithAppendAfterwards() { @@ -634,7 +634,7 @@ public void nestedLoops(int range) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "((x|^-?\\d+$))*yz", - realisticLevel = DYNAMIC, realisticStrings = ".*" + realisticLevel = DYNAMIC, realisticStrings = "(.*|.*yz)" ) }) public void stringBufferExample() { @@ -696,13 +696,13 @@ public void whileWithBreak(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", - realisticLevel = DYNAMIC, realisticStrings = ".*" + realisticLevel = DYNAMIC, realisticStrings = "((iv1|iv2): |.*)" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(iv1|iv2): ((great!)?)*(.*)?", realisticLevel = DYNAMIC, - realisticStrings = ".*" + realisticStrings = "(.*|.*.*)" ) }) public void extensive(boolean cond) { @@ -880,7 +880,7 @@ public void replaceExamples(int value) { ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "", - realisticLevel = DYNAMIC, realisticStrings = ".*" + realisticLevel = DYNAMIC, realisticStrings = "(.*|)" ), @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*", diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index f3e6ea1397..91e3263962 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -47,7 +47,9 @@ case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { override def simplify: StringTreeNode = { val simplifiedChild = child.simplify - if (simplifiedChild.isNeutralElement) + if (simplifiedChild.isInvalidElement) + StringTreeInvalidElement + else if (simplifiedChild.isNeutralElement) StringTreeNeutralElement else StringTreeRepetition(simplifiedChild) @@ -129,38 +131,6 @@ object StringTreeOr { def fromNodes(children: StringTreeNode*): StringTreeNode = new StringTreeOr(children).simplify } -case class StringTreeCond(child: StringTreeNode) extends StringTreeNode { - - override val children: Seq[StringTreeNode] = Seq(child) - - override def toRegex: String = { - val childRegex = child.toRegex - - // IMPROVE dont wrap and immediately unwrap in () - val resultingRegex = if (childRegex.startsWith("(") && childRegex.endsWith(")")) { - s"(${childRegex.substring(1, childRegex.length - 1)})?" - } else { - s"($childRegex)?" - } - - resultingRegex - } - - override def simplify: StringTreeNode = { - child.simplify match { - case condNode: StringTreeCond => condNode - case repetitionNode: StringTreeRepetition => repetitionNode - case node if node.isNeutralElement => StringTreeNeutralElement - case node => StringTreeCond(node) - } - } - - override def constancyLevel: StringConstancyLevel.Value = child.constancyLevel - - def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = - StringTreeCond(child.replaceParameters(parameters)) -} - sealed trait SimpleStringTreeNode extends StringTreeNode { override final val children: Seq[StringTreeNode] = Seq.empty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index f5ad2ad1e7..0506b16387 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -22,10 +22,8 @@ import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.tac.fpcf.analyses.string.flowanalysis.CyclicRegionType import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph -import org.opalj.tac.fpcf.analyses.string.flowanalysis.Region import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.properties.TACAI @@ -115,14 +113,6 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } private def computeNewUpperBound(state: ComputationState): MethodStringFlow = { - if (state.controlTree.nodes.exists(n => - n.outer.isInstanceOf[Region] && - n.outer.asInstanceOf[Region].regionType.isInstanceOf[CyclicRegionType] - ) - ) { - return MethodStringFlow.lb; - } - val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => val defPCs = web.defPCs.toList.sorted if (defPCs.head >= 0) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 1620d0b266..735910ad85 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -6,6 +6,7 @@ package analyses package string package flowanalysis +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment @@ -104,6 +105,66 @@ object DataFlowAnalysis { env.join(currentNodeEnvs(nextNode)) } + case Region(SelfLoop, _, entry) => + val resultEnv = pipe(entry, env) + // Looped operations that modify environment contents are not supported here + if (resultEnv != env) env.updateAll(StringTreeDynamicString) + else env + + case Region(WhileLoop, _, entry) => + val entryNode = limitedFlowGraph.get(entry) + val envAfterEntry = pipe(entry, env) + + var resultEnv = envAfterEntry + var currentNode = entryNode.diSuccessors.head + while (currentNode != entryNode) { + resultEnv = pipe(currentNode.outer, resultEnv) + currentNode = currentNode.diSuccessors.head + } + + // Looped operations that modify environment contents are not supported here + if (resultEnv != envAfterEntry) envAfterEntry.updateAll(StringTreeDynamicString) + else envAfterEntry + + case Region(NaturalLoop, _, entry) => + val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors + val removedBackEdgesGraph = limitedFlowGraph.filterNot( + edgeP = edge => + edge.sources.toList.toSet.intersect(entryPredecessors).nonEmpty + && edge.targets.contains(limitedFlowGraph.get(entry)) + ) + if (removedBackEdgesGraph.isCyclic) { + env.updateAll(StringTreeDynamicString) + } else { + // Handle resulting acyclic region + val entryNode = removedBackEdgesGraph.get(entry) + var sortedCurrentNodes = List(entryNode) + var currentNodeEnvs = Map((entryNode, pipe(entry, env))) + while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { + val nextNodeEnvs = sortedCurrentNodes.flatMap { node => + if (node.diSuccessors.isEmpty) { + Iterable((node, currentNodeEnvs(node))) + } else { + node.diSuccessors.toList.sortBy(_.outer).map { successor => + (successor, pipe(successor, currentNodeEnvs(node))) + } + } + } + sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) + currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => + env.join(otherEnv) + } + } + + val resultEnv = sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => + env.join(currentNodeEnvs(nextNode)) + } + + // Looped operations that modify string contents are not supported here + if (resultEnv != env) env.updateAll(StringTreeDynamicString) + else env + } + case _ => env } } From 93888e347917b28288774fd8221d13b0cdde4fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 3 Jun 2024 23:16:40 +0200 Subject: [PATCH 444/583] Rebuild for context oriented analysis --- .../info/StringAnalysisReflectiveCalls.scala | 16 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 12 +- .../properties/string/StringTreeNode.scala | 24 ++- .../analyses/string/ComputationState.scala | 12 -- .../string/MethodStringFlowAnalysis.scala | 22 ++- .../fpcf/analyses/string/StringAnalysis.scala | 144 ++++++++++++++++-- .../string/StringAnalysisScheduler.scala | 26 +++- .../analyses/string/StringAnalysisState.scala | 127 +++++++++++++++ .../flowanalysis/StructuralAnalysis.scala | 9 +- .../L0FunctionCallInterpreter.scala | 13 +- .../L0StaticFunctionCallInterpreter.scala | 11 +- .../L1FieldReadInterpreter.scala | 14 +- .../tac/fpcf/analyses/string/package.scala | 20 +-- 13 files changed, 380 insertions(+), 70 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.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 index 780acebf4a..359488d684 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 @@ -12,10 +12,14 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.Method import org.opalj.br.ReferenceType import org.opalj.br.analyses.BasicReport +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.Project import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.analyses.ReportableAnalysisResult +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyLevel import org.opalj.br.fpcf.properties.string.StringConstancyProperty @@ -40,7 +44,7 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey -import org.opalj.tac.fpcf.analyses.string.SContext +import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.properties.TACAI @@ -109,7 +113,10 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * analysis and the second element corresponds to the method name in which the entity occurred, * i.e., a value in [[relevantMethodNames]]. */ - private val entityContext = ListBuffer[(SContext, String)]() + private val entityContext = ListBuffer[(VariableContext, String)]() + + private var declaredMethods: DeclaredMethods = _ + private var contextProvider: ContextProvider = _ /** * A list of fully-qualified method names that are to be skipped, e.g., because they make an @@ -197,7 +204,8 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { call.descriptor.parameterTypes.zipWithIndex.foreach { case (ft, index) => if (ft.toJava == "java.lang.String") { - val e = (pc, call.params(index).asVar.toPersistentForm, method) + val context = contextProvider.newContext(declaredMethods(method)) + val e = VariableContext(pc, call.params(index).asVar.toPersistentForm, context) ps.force(e, StringConstancyProperty.key) entityContext.append((e, buildFQMethodName(call.declaringClass, call.name))) } @@ -307,6 +315,8 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) + contextProvider = project.get(ContextProviderKey) + declaredMethods = project.get(DeclaredMethodsKey) implicit val (propertyStore, _) = manager.runAll( if (runIntraproceduralAnalysis) LazyL0StringAnalysis.allRequiredAnalyses 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 bd0f05e419..2b16583326 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 @@ -14,13 +14,13 @@ import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey +import org.opalj.tac.PV import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis -import org.opalj.tac.fpcf.analyses.string.SEntity import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis @@ -43,7 +43,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, * [[extractPUVars]] is called with their [[TACode]]. */ - def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(Int, SEntity, Method)] = { + def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(Int, PV, Method)] = { val tacProvider = project.get(EagerDetachedTACAIKey) project.classHierarchy.allSuperclassesIterator( ObjectType(StringAnalysisTest.getAllowedFQTestMethodObjectTypeNameForLevel(level)), @@ -59,7 +59,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } - .foldLeft(Seq.empty[(Int, SEntity, Method)]) { (entities, m) => + .foldLeft(Seq.empty[(Int, PV, Method)]) { (entities, m) => entities ++ extractPUVars(tacProvider(m)).map(e => (e._1, e._2, m)) } } @@ -70,7 +70,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { * * @return Returns the arguments of the [[nameTestMethod]] as a PUVars list in the order in which they occurred. */ - def extractPUVars(tac: TACode[TACMethodParameter, V]): List[(Int, SEntity)] = { + def extractPUVars(tac: TACode[TACMethodParameter, V]): List[(Int, PV)] = { tac.cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) => allowedFQTestMethodsClassNames.exists(_ == declClass.toJavaClass.getName) && name == nameTestMethod @@ -79,9 +79,9 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } def determineEAS( - entities: Iterable[(Int, SEntity, Method)], + entities: Iterable[(Int, PV, Method)], project: Project[URL] - ): Iterable[((Int, SEntity, Method), String => String, List[Annotation])] = { + ): Iterable[((Int, PV, Method), String => String, List[Annotation])] = { val m2e = entities.groupBy(_._3).iterator.map(e => e._1 -> e._2.map(k => (k._1, k._2))).toMap // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 91e3263962..755a212446 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -22,6 +22,7 @@ sealed trait StringTreeNode { def constancyLevel: StringConstancyLevel.Value + def collectParameterIndices: Set[Int] = children.flatMap(_.collectParameterIndices).toSet def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode def isNeutralElement: Boolean = false @@ -57,6 +58,8 @@ case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def collectParameterIndices: Set[Int] = child.collectParameterIndices + def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = StringTreeRepetition(child.replaceParameters(parameters)) } @@ -128,7 +131,24 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } object StringTreeOr { - def fromNodes(children: StringTreeNode*): StringTreeNode = new StringTreeOr(children).simplify + def fromNodes(children: StringTreeNode*): StringTreeNode = { + val nonNeutralChildren = children.filterNot(_.isNeutralElement) + nonNeutralChildren.size match { + case 0 => StringTreeNeutralElement + case 1 => nonNeutralChildren.head + case _ => + var newChildren = Seq.empty[StringTreeNode] + nonNeutralChildren.foreach { + case orChild: StringTreeOr => newChildren :++= orChild.children + case child => newChildren :+= child + } + val distinctNewChildren = newChildren.distinct + distinctNewChildren.size match { + case 1 => distinctNewChildren.head + case _ => StringTreeOr(distinctNewChildren) + } + } + } } sealed trait SimpleStringTreeNode extends StringTreeNode { @@ -151,6 +171,8 @@ case class StringTreeConst(string: String) extends SimpleStringTreeNode { case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { override def toRegex: String = ".*" + override def collectParameterIndices: Set[Int] = Set(index) + override def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = parameters.getOrElse(index, this) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 7d8fbb8607..e5b14e9ba7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -15,21 +15,9 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty -/** - * 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 StringAnalysisState(entity: SContext, var stringFlowDependee: EOptionP[Method, MethodStringFlow]) { - - def hasDependees: Boolean = stringFlowDependee.isRefinable - def dependees: Set[EOptionP[Method, MethodStringFlow]] = Set(stringFlowDependee) -} - /** * 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 diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 0506b16387..1d6ce27f64 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -30,6 +30,7 @@ import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import org.opalj.util.PerformanceEvaluation.time /** * @author Maximilian Rüsch @@ -65,8 +66,12 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn implicit val tac: TAC = state.tac state.flowGraph = FlowGraph(tac.cfg) - val (_, superFlowGraph, controlTree) = + System.out.println(s"[ANALYSIS] Starting FlowGraph structural for graph nodes ${state.flowGraph.order} and total size ${state.flowGraph.size} for method ${state.dm}") + val (_, superFlowGraph, controlTree) = time { StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) + } { t => + System.out.println(s"[ANALYSIS] FlowGraph structural for graph nodes ${state.flowGraph.order} and total size ${state.flowGraph.size} took ${t.toSeconds} seconds!") + } state.superFlowGraph = superFlowGraph state.controlTree = controlTree @@ -127,10 +132,15 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } }.toMap) - MethodStringFlow(DataFlowAnalysis.compute( - state.controlTree, - state.superFlowGraph, - state.getFlowFunctionsByPC - )(startEnv)) + System.out.println(s"[ANALYSIS] Starting DATA FLOW for ct nodes ${state.controlTree.order} and total size ${state.controlTree.size} for method ${state.dm}") + time { + MethodStringFlow(DataFlowAnalysis.compute( + state.controlTree, + state.superFlowGraph, + state.getFlowFunctionsByPC + )(startEnv)) + } { t => + System.out.println(s"[ANALYSIS] DATA FLOW for ct nodes ${state.controlTree.order} and total size ${state.controlTree.size} took ${t.toSeconds} seconds!") + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 6cbe0633f4..ba5c1d8e6a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -5,32 +5,38 @@ package fpcf package analyses package string +import org.opalj.br.DeclaredMethod import org.opalj.br.Method import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.cg.NoCallers import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.EUBP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow /** * @author Maximilian Rüsch */ -class StringAnalysis(override val project: SomeProject) extends FPCFAnalysis { +private[string] class ContextFreeStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { - def analyze(data: SContext): ProperPropertyComputationResult = { - computeResults(StringAnalysisState(data, ps(data._3, MethodStringFlow.key))) - } + def analyze(vd: VariableDefinition): ProperPropertyComputationResult = + computeResults(ContextFreeStringAnalysisState(vd, ps(vd.m, MethodStringFlow.key))) - private def continuation(state: StringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(state: ContextFreeStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case _ if eps.pk.equals(MethodStringFlow.key) => + case _ if eps.pk == MethodStringFlow.key => state.stringFlowDependee = eps.asInstanceOf[EOptionP[Method, MethodStringFlow]] computeResults(state) @@ -39,13 +45,13 @@ class StringAnalysis(override val project: SomeProject) extends FPCFAnalysis { } } - private def computeResults(implicit state: StringAnalysisState): ProperPropertyComputationResult = { + private def computeResults(implicit state: ContextFreeStringAnalysisState): ProperPropertyComputationResult = { if (state.hasDependees) { InterimResult( state.entity, StringConstancyProperty.lb, computeNewUpperBound(state), - state.dependees.toSet, + state.dependees, continuation(state) ) } else { @@ -53,10 +59,128 @@ class StringAnalysis(override val project: SomeProject) extends FPCFAnalysis { } } - private def computeNewUpperBound(state: StringAnalysisState): StringConstancyProperty = { + private def computeNewUpperBound(state: ContextFreeStringAnalysisState): StringConstancyProperty = { StringConstancyProperty(state.stringFlowDependee match { - case UBP(methodStringFlow) => StringConstancyInformation(methodStringFlow(state.entity._1, state.entity._2)) + case UBP(methodStringFlow) => StringConstancyInformation(methodStringFlow(state.entity.pc, state.entity.pv)) case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub }) } } + +/** + * @author Maximilian Rüsch + */ +class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { + + private implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) + + def analyze(vc: VariableContext): ProperPropertyComputationResult = { + val vdScp = ps(VariableDefinition(vc.pc, vc.pv, vc.m), StringConstancyProperty.key) + continuation(ContextStringAnalysisState(vc, vdScp))(vdScp.asInstanceOf[SomeEPS]) + } + + private def continuation(state: ContextStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { + implicit val _state: ContextStringAnalysisState = state + eps match { + // "Downwards" dependency + case EUBP(_: VariableDefinition, _: StringConstancyProperty) => + state._stringDependee = eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]] + + val parameterIndices = state.stringTree.collectParameterIndices + if (parameterIndices.isEmpty) { + computeResults + } else { + // We have some parameters that need to be resolved for all callers of this method + val callersEOptP = ps(state.entity.context.method, Callers.key) + state._callersDependee = Some(callersEOptP) + + if (callersEOptP.hasUBP) { + handleNewCallers(NoCallers, callersEOptP.ub) + } + + computeResults + } + + case UBP(callers: Callers) => + val oldCallers = state._callersDependee.get.ub + state._callersDependee = Some(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) + handleNewCallers(oldCallers, callers) + computeResults + + case EUBP(m: Method, tacai: TACAI) => + handleTACAI(m, tacai) + computeResults + + case EUBP(_: VariableContext, _: StringConstancyProperty) => + state.updateParamDependee(eps.asInstanceOf[EOptionP[VariableContext, StringConstancyProperty]]) + computeResults + + case _ => + computeResults + } + } + + private def handleNewCallers( + oldCallers: Callers, + newCallers: Callers + )(implicit state: ContextStringAnalysisState): Unit = { + newCallers.forNewCallerContexts(oldCallers, state.dm) { (_, callerContext, pc, _) => + if (callerContext.method.hasSingleDefinedMethod) { + val callerMethod = callerContext.method.definedMethod + + System.out.println(s"FOUND RELEVANT CALLER FOR PC ${state.entity.pc} IN ${state.entity.m} FOR ${state.entity.pv}") + + val tacEOptP = ps(callerMethod, TACAI.key) + state.registerTacaiDepender(tacEOptP, (callerContext, pc)) + + if (tacEOptP.hasUBP) { + handleTACAI(callerMethod, tacEOptP.ub) + } + } else { + System.out.println(s"NON-SDM FOUND FOR PC ${state.entity.pc} IN ${state.entity.m} FOR ${state.entity.pv}") + } + } + } + + private def handleTACAI(m: Method, tacai: TACAI)( + implicit state: ContextStringAnalysisState + ): Unit = { + if (tacai.tac.isEmpty) { + state._discoveredUnknownTAC = true + } else { + val tac = tacai.tac.get + val (callerContext, pc) = state.getDepender(m) + val callExpr = tac.stmts(valueOriginOfPC(pc, tac.pcToIndex).get) match { + case Assignment(_, _, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] + case ExprStmt(_, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] + case call: Call[_] => call.asInstanceOf[Call[V]] + case node => throw new IllegalArgumentException(s"Unexpected argument: $node") + } + + for { + index <- state.stringTree.collectParameterIndices + } { + val paramVC = VariableContext( + pc, + callExpr.params(index).asVar.toPersistentForm(tac.stmts), + callerContext + ) + state.registerParameterDependee(index, m, ps(paramVC, StringConstancyProperty.key)) + } + } + } + + private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { + if (state.hasDependees) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty(state.currentUB), + state.dependees, + continuation(state) + ) + } else { + Result(state.entity, StringConstancyProperty(state.currentUB)) + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala index e48471d366..d03a93e653 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala @@ -8,10 +8,12 @@ package string import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler @@ -28,9 +30,12 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(MethodStringFlow)) - override final type InitializationData = StringAnalysis + override final type InitializationData = (ContextFreeStringAnalysis, ContextStringAnalysis) - override def init(p: SomeProject, ps: PropertyStore): InitializationData = new StringAnalysis(p) + override def init(p: SomeProject, ps: PropertyStore): InitializationData = ( + new ContextFreeStringAnalysis(p), + new ContextStringAnalysis(p) + ) override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -42,14 +47,23 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { object LazyStringAnalysis extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { - override def register(p: SomeProject, ps: PropertyStore, analysis: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) - analysis + override def register(p: SomeProject, ps: PropertyStore, data: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation( + StringConstancyProperty.key, + (entity: Entity) => { + entity match { + case vd: VariableDefinition => data._1.analyze(vd) + case vc: VariableContext => data._2.analyze(vc) + case e => throw new IllegalArgumentException(s"Cannot process entity $e") + } + } + ) + data._1 } override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - override def requiredProjectInformation: ProjectInformationKeys = Seq.empty + override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) } sealed trait MethodStringFlowAnalysisScheduler extends FPCFAnalysisScheduler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala new file mode 100644 index 0000000000..2d0974a3b6 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -0,0 +1,127 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string + +import scala.collection.mutable + +import org.opalj.br.DeclaredMethod +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.MethodStringFlow + +private[string] case class ContextFreeStringAnalysisState( + entity: VariableDefinition, + var stringFlowDependee: EOptionP[Method, MethodStringFlow] +) { + + def hasDependees: Boolean = stringFlowDependee.isRefinable + + def dependees: Set[EOptionP[Entity, Property]] = Set(stringFlowDependee) +} + +private[string] case class ContextStringAnalysisState( + entity: VariableContext, + var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] +) { + + def dm: DeclaredMethod = entity.context.method + def stringTree: StringTreeNode = _stringDependee.ub.sci.tree + + // Callers + var _callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = None + + // TACAI + private type TACAIDepender = (Context, Int) + private val _tacaiDependees: mutable.Map[Method, EOptionP[Method, TACAI]] = mutable.Map.empty + private val _tacaiDependers: mutable.Map[Method, TACAIDepender] = mutable.Map.empty + var _discoveredUnknownTAC: Boolean = false + + def registerTacaiDepender(tacEOptP: EOptionP[Method, TACAI], depender: TACAIDepender): Unit = { + _tacaiDependers(tacEOptP.e) = depender + _tacaiDependees(tacEOptP.e) = tacEOptP + } + def getDepender(m: Method): TACAIDepender = _tacaiDependers(m) + def updateTacaiDependee(tacEOptP: EOptionP[Method, TACAI]): Unit = _tacaiDependees(tacEOptP.e) = tacEOptP + + // Parameter StringConstancy + private val _entityToParamIndexMapping: mutable.Map[VariableContext, (Int, Method)] = mutable.Map.empty + private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[Method, VariableContext]] = + mutable.Map.empty.withDefaultValue(mutable.Map.empty) + private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = + mutable.Map.empty + + def registerParameterDependee( + index: Int, + m: Method, + dependee: EOptionP[VariableContext, StringConstancyProperty] + ): Unit = { + val previousParameterEntity = _paramIndexToEntityMapping(index).put(m, dependee.e) + if (previousParameterEntity.isDefined) { + _entityToParamIndexMapping.remove(previousParameterEntity.get) + } + + _entityToParamIndexMapping(dependee.e) = (index, m) + _paramDependees(dependee.e) = dependee + } + def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = { + if (_entityToParamIndexMapping.contains(dependee.e)) { + _paramDependees(dependee.e) = dependee + } else { + // When there is no parameter index for the dependee entity we lost track of it and do not need it anymore. + // Ensure no update is registered and the dependees are cleared from this EOptionP. + _paramDependees.remove(dependee.e) + } + } + + def currentUB: StringConstancyInformation = { + if (_stringDependee.hasUBP) { + val paramTrees = if (_discoveredUnknownTAC) { + stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap + } else { + stringTree.collectParameterIndices.map { index => + val paramTree = StringTreeOr { + _paramIndexToEntityMapping(index) + .valuesIterator.map(_paramDependees) + .filter(_.hasUBP).map(_.ub.sci.tree).toSeq + } + + (index, paramTree) + }.toMap + } + + StringConstancyInformation(stringTree.replaceParameters(paramTrees)) + } else { + StringConstancyInformation.ub + } + } + + def hasDependees: Boolean = _stringDependee.isRefinable || + _callersDependee.exists(_.isRefinable) || + _tacaiDependees.valuesIterator.exists(_.isRefinable) || + _paramDependees.valuesIterator.exists(_.isRefinable) + + def dependees: Set[EOptionP[Entity, Property]] = { + val preliminaryDependees = + _tacaiDependees.valuesIterator.filter(_.isRefinable) ++ + _paramDependees.valuesIterator.filter(_.isRefinable) ++ + _callersDependee.filter(_.isRefinable) + + if (_stringDependee.isRefinable) { + preliminaryDependees.toSet + _stringDependee + } else { + preliminaryDependees.toSet + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 8a8bc1a997..636428722a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -27,7 +27,7 @@ object StructuralAnalysis { var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] var outerIterations = 0 - while (g.order > 1 && outerIterations < 100) { + while (g.order > 1 && outerIterations < 10000) { // Find post order depth first traversal order for nodes var postCtr = 1 val post = mutable.ListBuffer.empty[FlowGraphNode] @@ -157,11 +157,14 @@ object StructuralAnalysis { } else { val graphWithoutN = graph.excl(n) graphWithoutN.nodes.outerIterable.exists { k => + val edgeOpt = graph.find(DiEdge(k, n)) + graphWithoutN.get(m).pathTo( graphWithoutN.get(k) ).isDefined && - graph.edges.toOuter.contains(DiEdge(k, n)) && - (k == n || domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k))) + edgeOpt.isDefined && + graph.edges.contains(edgeOpt.get) && + (k == n || domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k))) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index 2477592636..df063ddd8f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -40,13 +40,16 @@ trait L0FunctionCallInterpreter calleeMethods: Seq[Method], parameters: Seq[PV], var tacDependees: Map[Method, EOptionP[Method, TACAI]], - var returnDependees: Map[Method, Seq[EOptionP[SContext, StringConstancyProperty]]] = Map.empty + var returnDependees: Map[Method, Seq[EOptionP[VariableDefinition, StringConstancyProperty]]] = Map.empty ) { def pc: Int = state.pc var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) - def updateReturnDependee(method: Method, newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { + def updateReturnDependee( + method: Method, + newDependee: EOptionP[VariableDefinition, StringConstancyProperty] + ): Unit = { returnDependees = returnDependees.updated( method, returnDependees(method).updated( @@ -85,7 +88,7 @@ trait L0FunctionCallInterpreter callState.hasUnresolvableReturnValue += m -> true } else { callState.returnDependees += m -> returns.map { ret => - val entity: SContext = ( + val entity = VariableDefinition( ret.pc, ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), m @@ -145,8 +148,8 @@ trait L0FunctionCallInterpreter interpretArbitraryCallToFunctions(callState) case EUBP(_, _: StringConstancyProperty) => - val contextEPS = eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]] - callState.updateReturnDependee(contextEPS.e._3, contextEPS) + val contextEPS = eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]] + callState.updateReturnDependee(contextEPS.e.m, contextEPS) tryComputeFinalResult(callState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 0a03cadffe..f1dacd1796 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -14,6 +14,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** @@ -32,9 +33,17 @@ case class L0StaticFunctionCallInterpreter()( override def interpretExpr(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { + if (call.name == "executePrivileged") { + System.out.println("FOUND A PRIVILEDGED EXECUTION!") + } + call.name match { case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) - case _ => interpretArbitraryCall(target, call) + case "getProperty" if call.declaringClass == ObjectType("java/util/Properties") => + System.out.println("TRACED STRING ANALYSIS FOR SYSTEM PROPERTY CALL!") + interpretArbitraryCall(target, call) + case _ if call.descriptor.returnType == ObjectType.String => interpretArbitraryCall(target, call) + case _ => computeFinalResult(StringFlowFunctionProperty.identity) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 0e7578c079..1a1c7ee71e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -73,13 +73,13 @@ case class L1FieldReadInterpreter( private case class FieldReadState( target: PV, - var hasWriteInSameMethod: Boolean = false, - var hasInit: Boolean = false, - var hasUnresolvableAccess: Boolean = false, - var accessDependees: Seq[EOptionP[SContext, StringConstancyProperty]] = Seq.empty + var hasWriteInSameMethod: Boolean = false, + var hasInit: Boolean = false, + var hasUnresolvableAccess: Boolean = false, + var accessDependees: Seq[EOptionP[VariableDefinition, StringConstancyProperty]] = Seq.empty ) { - def updateAccessDependee(newDependee: EOptionP[SContext, StringConstancyProperty]): Unit = { + def updateAccessDependee(newDependee: EOptionP[VariableDefinition, StringConstancyProperty]): Unit = { accessDependees = accessDependees.updated( accessDependees.indexWhere(_.e == newDependee.e), newDependee @@ -138,7 +138,7 @@ case class L1FieldReadInterpreter( // Field parameter information is not available accessState.hasUnresolvableAccess = true } else { - val entity: SContext = (pc, PUVar(parameter.get._1, parameter.get._2), method) + val entity = VariableDefinition(pc, PUVar(parameter.get._1, parameter.get._2), method) accessState.accessDependees = accessState.accessDependees :+ ps(entity, StringConstancyProperty.key) } } @@ -188,7 +188,7 @@ case class L1FieldReadInterpreter( )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(_: StringConstancyProperty) => - accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[SContext, StringConstancyProperty]]) + accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]]) tryComputeFinalResult(accessState, state) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index edcf5ce1e4..aa8fea5c48 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -6,6 +6,7 @@ package analyses import org.opalj.br.DefinedMethod import org.opalj.br.Method +import org.opalj.br.fpcf.properties.Context /** * @author Maximilian Rüsch @@ -14,17 +15,16 @@ package object string { type TAC = TACode[TACMethodParameter, V] - /** - * The type of entities the string analysis process. - * - * @note The analysis require further context information, see [[SContext]]. - */ - type SEntity = PV + trait AnalyzableVariable { + def pc: Int + def pv: PV + def m: Method + } - /** - * String analysis process a local variable within a particular context, i.e. the method in which it is used. - */ - type SContext = (Int, SEntity, Method) + private[string] case class VariableDefinition(pc: Int, pv: PV, m: Method) extends AnalyzableVariable + case class VariableContext(pc: Int, pv: PV, context: Context) extends AnalyzableVariable { + override def m: Method = context.method.definedMethod + } case class MethodPC(pc: Int, dm: DefinedMethod) } From 9d0624cc45fd012ef94bb85c0d29d8d17e3b86c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 4 Jun 2024 22:04:26 +0200 Subject: [PATCH 445/583] Fix the parameter read tests --- .../org/opalj/fpcf/StringAnalysisTest.scala | 31 ++++++++++++------- .../properties/string/StringTreeNode.scala | 9 ++++++ .../fpcf/analyses/string/StringAnalysis.scala | 6 ++-- .../analyses/string/StringAnalysisState.scala | 25 ++++++++++----- 4 files changed, 50 insertions(+), 21 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 2b16583326..b805f115cd 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 @@ -10,8 +10,10 @@ import org.opalj.br.Annotation import org.opalj.br.Annotations import org.opalj.br.Method import org.opalj.br.ObjectType +import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project +import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV @@ -21,6 +23,7 @@ import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis +import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis @@ -43,8 +46,10 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, * [[extractPUVars]] is called with their [[TACode]]. */ - def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(Int, PV, Method)] = { + def determineEntitiesToAnalyze(project: Project[URL]): Iterable[VariableContext] = { val tacProvider = project.get(EagerDetachedTACAIKey) + val declaredMethods = project.get(DeclaredMethodsKey) + val contextProvider = project.get(ContextProviderKey) project.classHierarchy.allSuperclassesIterator( ObjectType(StringAnalysisTest.getAllowedFQTestMethodObjectTypeNameForLevel(level)), reflexive = true @@ -59,8 +64,10 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } - .foldLeft(Seq.empty[(Int, PV, Method)]) { (entities, m) => - entities ++ extractPUVars(tacProvider(m)).map(e => (e._1, e._2, m)) + .foldLeft(Seq.empty[VariableContext]) { (entities, m) => + entities ++ extractPUVars(tacProvider(m)).map(e => + VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m))) + ) } } @@ -79,16 +86,18 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } def determineEAS( - entities: Iterable[(Int, PV, Method)], + entities: Iterable[VariableContext], project: Project[URL] - ): Iterable[((Int, PV, Method), String => String, List[Annotation])] = { - val m2e = entities.groupBy(_._3).iterator.map(e => e._1 -> e._2.map(k => (k._1, k._2))).toMap + ): Iterable[(VariableContext, String => String, List[Annotation])] = { + val m2e = entities.groupBy(_.context).iterator.map(e => + e._1.method.definedMethod -> (e._1, e._2.map(k => (k.pc, k.pv))) + ).toMap // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => - m2e(am._1).zipWithIndex.map { + m2e(am._1)._2.zipWithIndex.map { case ((pc, puVar), index) => Tuple3( - (pc, puVar, am._1), + VariableContext(pc, puVar, m2e(am._1)._1), { s: String => s"${am._2(s)} (#$index)" }, List(StringAnalysisTest.getStringDefinitionsFromCollection(am._3, index)) ) @@ -201,9 +210,9 @@ class L1StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) // Currently broken L1 Tests - .filterNot(entity => entity._3.name.startsWith("cyclicDependencyTest")) - .filterNot(entity => entity._3.name.startsWith("unknownHierarchyInstanceTest")) - .filterNot(entity => entity._3.name.startsWith("crissCrossExample")) + .filterNot(entity => entity.context.method.name.startsWith("cyclicDependencyTest")) + .filterNot(entity => entity.context.method.name.startsWith("unknownHierarchyInstanceTest")) + .filterNot(entity => entity.context.method.name.startsWith("crissCrossExample")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 755a212446..1f2b3ee20b 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -131,6 +131,15 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } object StringTreeOr { + + def apply(children: Seq[StringTreeNode]): StringTreeNode = { + if (children.isEmpty) { + StringTreeNeutralElement + } else { + new StringTreeOr(children) + } + } + def fromNodes(children: StringTreeNode*): StringTreeNode = { val nonNeutralChildren = children.filterNot(_.isNeutralElement) nonNeutralChildren.size match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index ba5c1d8e6a..2240465be6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -125,7 +125,7 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly newCallers: Callers )(implicit state: ContextStringAnalysisState): Unit = { newCallers.forNewCallerContexts(oldCallers, state.dm) { (_, callerContext, pc, _) => - if (callerContext.method.hasSingleDefinedMethod) { + if (callerContext.hasContext && callerContext.method.hasSingleDefinedMethod) { val callerMethod = callerContext.method.definedMethod System.out.println(s"FOUND RELEVANT CALLER FOR PC ${state.entity.pc} IN ${state.entity.m} FOR ${state.entity.pv}") @@ -175,12 +175,12 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly InterimResult( state.entity, StringConstancyProperty.lb, - StringConstancyProperty(state.currentUB), + StringConstancyProperty(state.currentSciUB), state.dependees, continuation(state) ) } else { - Result(state.entity, StringConstancyProperty(state.currentUB)) + Result(state.entity, StringConstancyProperty(state.finalSci)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 2d0974a3b6..ccf0b1a86f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.Entity @@ -85,19 +86,20 @@ private[string] case class ContextStringAnalysisState( } } - def currentUB: StringConstancyInformation = { + def currentSciUB: StringConstancyInformation = { if (_stringDependee.hasUBP) { val paramTrees = if (_discoveredUnknownTAC) { stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap } else { stringTree.collectParameterIndices.map { index => - val paramTree = StringTreeOr { - _paramIndexToEntityMapping(index) - .valuesIterator.map(_paramDependees) - .filter(_.hasUBP).map(_.ub.sci.tree).toSeq - } + val paramOptions = _paramIndexToEntityMapping(index) + .valuesIterator.map(_paramDependees) + .filter(_.hasUBP).map(_.ub.sci.tree).toSeq - (index, paramTree) + val paramTree = if (paramOptions.nonEmpty) StringTreeOr(paramOptions) + else StringTreeNeutralElement + + (index, paramTree.simplify) }.toMap } @@ -107,6 +109,15 @@ private[string] case class ContextStringAnalysisState( } } + def finalSci: StringConstancyInformation = { + if (_paramIndexToEntityMapping.valuesIterator.map(_.size).sum == 0) { + val paramTrees = stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap + StringConstancyInformation(stringTree.replaceParameters(paramTrees)) + } else { + currentSciUB + } + } + def hasDependees: Boolean = _stringDependee.isRefinable || _callersDependee.exists(_.isRefinable) || _tacaiDependees.valuesIterator.exists(_.isRefinable) || From aa1d43da8702e5862e98ef635ded8f284001daba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 4 Jun 2024 22:18:28 +0200 Subject: [PATCH 446/583] Fix missing simplify in tests --- .../properties/string_analysis/StringAnalysisMatcher.scala | 4 ++-- .../org/opalj/br/fpcf/properties/string/StringTreeNode.scala | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index 2ef4b81e5d..b81b2dc99f 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -71,8 +71,8 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { val (actLevel, actString) = properties.head match { case prop: StringConstancyProperty => - val sci = prop.stringConstancyInformation - (sci.constancyLevel.toString.toLowerCase, sci.toRegex) + val tree = prop.sci.tree.simplify + (tree.constancyLevel.toString.toLowerCase, tree.toRegex) case _ => ("", "") } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 1f2b3ee20b..ee5d59e922 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -175,6 +175,8 @@ case class StringTreeConst(string: String) extends SimpleStringTreeNode { override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT def isIntConst: Boolean = Try(string.toInt).isSuccess + + override def isNeutralElement: Boolean = string == "" } case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { From 87ad517fff0e99a2d13179b147e0d9662e4e6b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 4 Jun 2024 22:35:05 +0200 Subject: [PATCH 447/583] Fix remaining static call based tests --- .../opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java | 2 +- .../src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 2 ++ .../l0/interpretation/L0StaticFunctionCallInterpreter.scala | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 4c0e688777..8762ec2b7e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -880,7 +880,7 @@ public void replaceExamples(int value) { ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "", - realisticLevel = DYNAMIC, realisticStrings = "(.*|)" + realisticLevel = DYNAMIC, realisticStrings = ".*" ), @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*", 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 b805f115cd..2a4a0bf53a 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 @@ -169,6 +169,8 @@ class L0StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) val newEntities = entities + // .filter(entity => entity.context.method.name == "tryCatchFinally") + // .take(1) // it("can be executed without exceptions") { newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index f1dacd1796..1a18304a8f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -43,7 +43,7 @@ case class L0StaticFunctionCallInterpreter()( System.out.println("TRACED STRING ANALYSIS FOR SYSTEM PROPERTY CALL!") interpretArbitraryCall(target, call) case _ if call.descriptor.returnType == ObjectType.String => interpretArbitraryCall(target, call) - case _ => computeFinalResult(StringFlowFunctionProperty.identity) + case _ => computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) } } } From 63e1f33f5b523350b1263ac93401e21a97dced98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 5 Jun 2024 20:43:23 +0200 Subject: [PATCH 448/583] Fix post order traversal and implement more efficient cycle check --- .../flowanalysis/StructuralAnalysis.scala | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 636428722a..e1dcdaf5ad 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -75,12 +75,13 @@ object StructuralAnalysis { (newGraph, newSuperGraph, newRegion) } - PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry)(post.append) + PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry) { post.append } while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, n) + val gPost = post.map(g.get).reverse.asInstanceOf[mutable.ListBuffer[FlowGraph#NodeT]] + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPost, n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -169,9 +170,10 @@ object StructuralAnalysis { } } - private def locateAcyclicRegion[A, G <: Graph[A, DiEdge[A]]]( - graph: G, - startingNode: A + private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( + graph: G, + postOrderTraversal: mutable.ListBuffer[G#NodeT], + startingNode: A ): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] var entry: graph.NodeT = graph.get(startingNode) @@ -201,13 +203,24 @@ object StructuralAnalysis { def locateProperAcyclicInterval: Option[AcyclicRegionType] = { var currentNodeSet = Set(n) var currentSuccessors = n.diSuccessors - while (currentSuccessors.size > 1 && graph.filter(node => currentNodeSet.contains(node)).isAcyclic) { + + def isStillAcyclic: Boolean = { + currentSuccessors.forall { node => + val postOrderIndex = postOrderTraversal.indexOf(node) + + node.diSuccessors.forall(successor => postOrderTraversal.indexOf(successor) >= postOrderIndex) + } + } + + var stillAcyclic = isStillAcyclic + while (currentSuccessors.size > 1 && stillAcyclic) { currentNodeSet = currentNodeSet ++ currentSuccessors currentSuccessors = currentSuccessors.flatMap(node => node.diSuccessors) + stillAcyclic = isStillAcyclic } val allPredecessors = currentNodeSet.excl(n).flatMap(node => node.diPredecessors) - if (graph.filter(node => currentNodeSet.contains(node)).isCyclic) { + if (!stillAcyclic) { None } else if (!allPredecessors.equals(currentNodeSet.diff(currentSuccessors))) { None @@ -302,24 +315,28 @@ object StructuralAnalysis { object PostOrderTraversal { - /** - * @note This function should be kept stable with regards to an ordering on the given graph nodes. - */ - private def foreachInTraversal[A, G <: Graph[A, DiEdge[A]]]( - graph: G, - toVisit: Seq[A], - visited: Set[A] - )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { - if (toVisit.nonEmpty) { - val next = toVisit.head - val nextSuccessors = (graph.get(next).diSuccessors.map(_.outer) -- visited -- toVisit).toList.sorted - - foreachInTraversal(graph, nextSuccessors ++ toVisit.tail, visited + next)(nodeHandler) - nodeHandler(next) - } - } - + /** @note This function should be kept stable with regards to an ordering on the given graph nodes. */ def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( implicit ordering: Ordering[A] - ): Unit = foreachInTraversal(graph, Seq(initial), Set.empty)(nodeHandler) + ): Unit = { + var visited = Set.empty[A] + + def foreachInTraversal( + graph: G, + node: A + )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { + visited = visited + node + + for { + successor <- (graph.get(node).diSuccessors.map(_.outer) -- visited).toList.sorted + if !visited.contains(successor) + } { + foreachInTraversal(graph, successor)(nodeHandler) + } + + nodeHandler(node) + } + + foreachInTraversal(graph, initial)(nodeHandler) + } } From d867a547c3abe07f21f9ef6e2441045ffeafb5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 5 Jun 2024 21:11:09 +0200 Subject: [PATCH 449/583] Implement much more efficient back edge path detection --- .../string/flowanalysis/StructuralAnalysis.scala | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index e1dcdaf5ad..e7d37e45cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -156,16 +156,12 @@ object StructuralAnalysis { if (m == n) { false } else { - val graphWithoutN = graph.excl(n) - graphWithoutN.nodes.outerIterable.exists { k => - val edgeOpt = graph.find(DiEdge(k, n)) - - graphWithoutN.get(m).pathTo( - graphWithoutN.get(k) - ).isDefined && - edgeOpt.isDefined && - graph.edges.contains(edgeOpt.get) && - (k == n || domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k))) + val nonNFromMTraverser = graph.innerNodeTraverser(graph.get(m), subgraphNodes = _.outer != n) + graph.nodes.outerIterable.exists { k => + k != n && + graph.find(DiEdge(k, n)).isDefined && + nonNFromMTraverser.pathTo(graph.get(k)).isDefined && + domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k)) } } } From ef57f722d31312df7065361dbcec6cfcc483301e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 5 Jun 2024 21:23:15 +0200 Subject: [PATCH 450/583] Refactor out individual processing functions in data flow analysis --- .../flowanalysis/DataFlowAnalysis.scala | 191 ++++++++++-------- 1 file changed, 102 insertions(+), 89 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 735910ad85..5e63e64c5f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -42,49 +42,104 @@ object DataFlowAnalysis { val childNodes = controlTree.get(node).diSuccessors.map(_.outer) val limitedFlowGraph = superFlowGraph.filter(n => childNodes.contains(n.outer)) - node match { - case Statement(pc) if pc >= 0 => flowFunctionByPc(pc)(env) - case Statement(_) => env - - case Region(Block, _, entry) => - var currentEnv = env - var currentNode = limitedFlowGraph.get(entry) - while (currentNode.diSuccessors.nonEmpty) { - currentEnv = pipe(currentNode.outer, currentEnv) - currentNode = currentNode.diSuccessors.head - } + def processBlock(entry: FlowGraphNode): StringTreeEnvironment = { + var currentEnv = env + var currentNode = limitedFlowGraph.get(entry) + while (currentNode.diSuccessors.nonEmpty) { + currentEnv = pipe(currentNode.outer, currentEnv) + currentNode = currentNode.diSuccessors.head + } + + pipe(currentNode, currentEnv) + } - pipe(currentNode, currentEnv) + def processIfThenElse(entry: FlowGraphNode): StringTreeEnvironment = { + val entryNode = limitedFlowGraph.get(entry) + val successors = entryNode.diSuccessors.map(_.outer).toList.sorted + val branches = (successors.head, successors.tail.head) - case Region(IfThenElse, _, entry) => - val entryNode = limitedFlowGraph.get(entry) - val successors = entryNode.diSuccessors.map(_.outer).toList.sorted - val branches = (successors.head, successors.tail.head) + val envAfterEntry = pipe(entry, env) + val envAfterBranches = (pipe(branches._1, envAfterEntry), pipe(branches._2, envAfterEntry)) - val envAfterEntry = pipe(entry, env) - val envAfterBranches = (pipe(branches._1, envAfterEntry), pipe(branches._2, envAfterEntry)) + envAfterBranches._1.join(envAfterBranches._2) + } - envAfterBranches._1.join(envAfterBranches._2) + def processIfThen(entry: FlowGraphNode): StringTreeEnvironment = { + val entryNode = limitedFlowGraph.get(entry) + val (yesBranch, noBranch) = if (entryNode.diSuccessors.head.diSuccessors.nonEmpty) { + (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) + } else { + (entryNode.diSuccessors.tail.head, entryNode.diSuccessors.head) + } + + val envAfterEntry = pipe(entry, env) + val envAfterBranches = ( + pipe(yesBranch.diSuccessors.head, pipe(yesBranch, envAfterEntry)), + pipe(noBranch, envAfterEntry) + ) + + envAfterBranches._1.join(envAfterBranches._2) + } - case Region(IfThen, _, entry) => - val entryNode = limitedFlowGraph.get(entry) - val (yesBranch, noBranch) = if (entryNode.diSuccessors.head.diSuccessors.nonEmpty) { - (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) - } else { - (entryNode.diSuccessors.tail.head, entryNode.diSuccessors.head) + def processProper(entry: FlowGraphNode): StringTreeEnvironment = { + val entryNode = limitedFlowGraph.get(entry) + + var sortedCurrentNodes = List(entryNode) + var currentNodeEnvs = Map((entryNode, pipe(entry, env))) + while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { + val nextNodeEnvs = sortedCurrentNodes.flatMap { node => + if (node.diSuccessors.isEmpty) { + Iterable((node, currentNodeEnvs(node))) + } else { + node.diSuccessors.toList.sortBy(_.outer).map { successor => + (successor, pipe(successor, currentNodeEnvs(node))) + } + } } + sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) + currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } + } - val envAfterEntry = pipe(entry, env) - val envAfterBranches = ( - pipe(yesBranch.diSuccessors.head, pipe(yesBranch, envAfterEntry)), - pipe(noBranch, envAfterEntry) - ) + sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => + env.join(currentNodeEnvs(nextNode)) + } + } - envAfterBranches._1.join(envAfterBranches._2) + def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { + val resultEnv = pipe(entry, env) + // Looped operations that modify environment contents are not supported here + if (resultEnv != env) env.updateAll(StringTreeDynamicString) + else env + } - case Region(Proper, _, entry) => - val entryNode = limitedFlowGraph.get(entry) + def processWhileLoop(entry: FlowGraphNode): StringTreeEnvironment = { + val entryNode = limitedFlowGraph.get(entry) + val envAfterEntry = pipe(entry, env) + + var resultEnv = envAfterEntry + var currentNode = entryNode.diSuccessors.head + while (currentNode != entryNode) { + resultEnv = pipe(currentNode.outer, resultEnv) + currentNode = currentNode.diSuccessors.head + } + + // Looped operations that modify environment contents are not supported here + if (resultEnv != envAfterEntry) envAfterEntry.updateAll(StringTreeDynamicString) + else envAfterEntry + } + def processNaturalLoop(entry: FlowGraphNode): StringTreeEnvironment = { + val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors + val removedBackEdgesGraph = limitedFlowGraph.filterNot( + edgeP = edge => + edge.sources.toList.toSet.intersect(entryPredecessors).nonEmpty + && edge.targets.contains(limitedFlowGraph.get(entry)) + ) + if (removedBackEdgesGraph.isCyclic) { + env.updateAll(StringTreeDynamicString) + } else { + // Handle resulting acyclic region + val entryNode = removedBackEdgesGraph.get(entry) var sortedCurrentNodes = List(entryNode) var currentNodeEnvs = Map((entryNode, pipe(entry, env))) while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { @@ -101,69 +156,27 @@ object DataFlowAnalysis { currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } } - sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => + val resultEnv = sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => env.join(currentNodeEnvs(nextNode)) } - case Region(SelfLoop, _, entry) => - val resultEnv = pipe(entry, env) - // Looped operations that modify environment contents are not supported here + // Looped operations that modify string contents are not supported here if (resultEnv != env) env.updateAll(StringTreeDynamicString) else env + } + } - case Region(WhileLoop, _, entry) => - val entryNode = limitedFlowGraph.get(entry) - val envAfterEntry = pipe(entry, env) - - var resultEnv = envAfterEntry - var currentNode = entryNode.diSuccessors.head - while (currentNode != entryNode) { - resultEnv = pipe(currentNode.outer, resultEnv) - currentNode = currentNode.diSuccessors.head - } - - // Looped operations that modify environment contents are not supported here - if (resultEnv != envAfterEntry) envAfterEntry.updateAll(StringTreeDynamicString) - else envAfterEntry - - case Region(NaturalLoop, _, entry) => - val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors - val removedBackEdgesGraph = limitedFlowGraph.filterNot( - edgeP = edge => - edge.sources.toList.toSet.intersect(entryPredecessors).nonEmpty - && edge.targets.contains(limitedFlowGraph.get(entry)) - ) - if (removedBackEdgesGraph.isCyclic) { - env.updateAll(StringTreeDynamicString) - } else { - // Handle resulting acyclic region - val entryNode = removedBackEdgesGraph.get(entry) - var sortedCurrentNodes = List(entryNode) - var currentNodeEnvs = Map((entryNode, pipe(entry, env))) - while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { - val nextNodeEnvs = sortedCurrentNodes.flatMap { node => - if (node.diSuccessors.isEmpty) { - Iterable((node, currentNodeEnvs(node))) - } else { - node.diSuccessors.toList.sortBy(_.outer).map { successor => - (successor, pipe(successor, currentNodeEnvs(node))) - } - } - } - sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) - currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => - env.join(otherEnv) - } - } - - val resultEnv = sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => - env.join(currentNodeEnvs(nextNode)) - } + node match { + case Statement(pc) if pc >= 0 => flowFunctionByPc(pc)(env) + case Statement(_) => env - // Looped operations that modify string contents are not supported here - if (resultEnv != env) env.updateAll(StringTreeDynamicString) - else env - } + case Region(Block, _, entry) => processBlock(entry) + case Region(IfThenElse, _, entry) => processIfThenElse(entry) + case Region(IfThen, _, entry) => processIfThen(entry) + case Region(Proper, _, entry) => processProper(entry) + case Region(SelfLoop, _, entry) => processSelfLoop(entry) + case Region(WhileLoop, _, entry) => processWhileLoop(entry) + case Region(NaturalLoop, _, entry) => processNaturalLoop(entry) case _ => env } From d21898dce96b54a5945d2b474f51e9c9f13b3128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 5 Jun 2024 22:23:04 +0200 Subject: [PATCH 451/583] Optimize data flow analysis --- .../properties/string/StringTreeNode.scala | 8 +++--- .../flowanalysis/DataFlowAnalysis.scala | 6 +++-- .../string/StringTreeEnvironment.scala | 25 +++++++++++++------ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index ee5d59e922..6ee199a5ed 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -141,13 +141,13 @@ object StringTreeOr { } def fromNodes(children: StringTreeNode*): StringTreeNode = { - val nonNeutralChildren = children.filterNot(_.isNeutralElement) - nonNeutralChildren.size match { + val nonNeutralDistinctChildren = children.distinct.filterNot(_.isNeutralElement) + nonNeutralDistinctChildren.size match { case 0 => StringTreeNeutralElement - case 1 => nonNeutralChildren.head + case 1 => nonNeutralDistinctChildren.head case _ => var newChildren = Seq.empty[StringTreeNode] - nonNeutralChildren.foreach { + nonNeutralDistinctChildren.foreach { case orChild: StringTreeOr => newChildren :++= orChild.children case child => newChildren :+= child } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 5e63e64c5f..49f1a1fe93 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -96,8 +96,10 @@ object DataFlowAnalysis { } } } - sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) - currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } + sortedCurrentNodes = nextNodeEnvs.map(_._1).distinct.sortBy(_.outer) + currentNodeEnvs = nextNodeEnvs.groupBy(_._1) map { kv => + (kv._1, kv._2.head._2.joinMany(kv._2.tail.map(_._2))) + } } sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 51b68e473f..387590867f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -54,15 +54,26 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { def updateAll(value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.map { kv => (kv._1, value) }) def join(other: StringTreeEnvironment): StringTreeEnvironment = { - val result = (map.keySet ++ other.map.keys) map { web => - (map.get(web), other.map.get(web)) match { - case (Some(value1), Some(value2)) => web -> StringTreeOr.fromNodes(value1, value2) - case (Some(value1), None) => web -> value1 - case (None, Some(value2)) => web -> value2 - case (None, None) => throw new IllegalStateException("Found a key in a map that is not in the map!") + val (smallMap, bigMap) = if (map.size < other.map.size) (map, other.map) else (other.map, map) + + StringTreeEnvironment { + bigMap.toList.concat(smallMap.toList).groupBy(_._1).map { + case (k, vs) if vs.take(2).size == 1 => (k, vs.head._2) + case (k, vs) => (k, StringTreeOr.fromNodes(vs.map(_._2): _*)) } } + } - StringTreeEnvironment(result.toMap) + def joinMany(others: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { + if (others.isEmpty) { + this + } else { + StringTreeEnvironment { + map.toList.concat(others.flatMap(_.map.toList)).groupBy(_._1).map { + case (k, vs) if vs.take(2).size == 1 => (k, vs.head._2) + case (k, vs) => (k, StringTreeOr.fromNodes(vs.map(_._2): _*)) + } + } + } } } From 37f79595008a8383e8934e35f6fc441264d575b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 5 Jun 2024 22:24:18 +0200 Subject: [PATCH 452/583] Remove timing debug statements --- .../string/MethodStringFlowAnalysis.scala | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 1d6ce27f64..0506b16387 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -30,7 +30,6 @@ import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment -import org.opalj.util.PerformanceEvaluation.time /** * @author Maximilian Rüsch @@ -66,12 +65,8 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn implicit val tac: TAC = state.tac state.flowGraph = FlowGraph(tac.cfg) - System.out.println(s"[ANALYSIS] Starting FlowGraph structural for graph nodes ${state.flowGraph.order} and total size ${state.flowGraph.size} for method ${state.dm}") - val (_, superFlowGraph, controlTree) = time { + val (_, superFlowGraph, controlTree) = StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) - } { t => - System.out.println(s"[ANALYSIS] FlowGraph structural for graph nodes ${state.flowGraph.order} and total size ${state.flowGraph.size} took ${t.toSeconds} seconds!") - } state.superFlowGraph = superFlowGraph state.controlTree = controlTree @@ -132,15 +127,10 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } }.toMap) - System.out.println(s"[ANALYSIS] Starting DATA FLOW for ct nodes ${state.controlTree.order} and total size ${state.controlTree.size} for method ${state.dm}") - time { - MethodStringFlow(DataFlowAnalysis.compute( - state.controlTree, - state.superFlowGraph, - state.getFlowFunctionsByPC - )(startEnv)) - } { t => - System.out.println(s"[ANALYSIS] DATA FLOW for ct nodes ${state.controlTree.order} and total size ${state.controlTree.size} took ${t.toSeconds} seconds!") - } + MethodStringFlow(DataFlowAnalysis.compute( + state.controlTree, + state.superFlowGraph, + state.getFlowFunctionsByPC + )(startEnv)) } } From b8519f561ec2f40178ba3ecba1e049c4ef2147d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 19:40:27 +0200 Subject: [PATCH 453/583] Remove filters on L0 tests --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 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 2a4a0bf53a..7ecdf6f64e 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 @@ -168,18 +168,12 @@ class L0StringAnalysisTest extends StringAnalysisTest { val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) - val newEntities = entities - // .filter(entity => entity.context.method.name == "tryCatchFinally") - // .take(1) - - // it("can be executed without exceptions") { - newEntities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() - validateProperties(as, determineEAS(newEntities, as.project), Set("StringConstancy")) - // } + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) } } From c9acbc3566fe5270f319b8f6b9e3da2d440c85c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 19:41:01 +0200 Subject: [PATCH 454/583] Correctly handle dependencies in string analysis --- .../analyses/string/MethodStringFlowAnalysis.scala | 6 +++--- .../tac/fpcf/analyses/string/StringAnalysis.scala | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 0506b16387..991f8cefe6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -15,9 +15,9 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EUBP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimEUB import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -86,12 +86,12 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] determinePossibleStrings(state) - case InterimEUB(e: MethodPC) if eps.pk.equals(StringFlowFunctionProperty.key) => + case EUBP(e: MethodPC, _: StringFlowFunctionProperty) if eps.pk.equals(StringFlowFunctionProperty.key) => state.updateDependee(e.pc, eps.asInstanceOf[EOptionP[MethodPC, StringFlowFunctionProperty]]) computeResults(state) case _ => - getInterimResult(state) + throw new IllegalArgumentException(s"Unknown EPS given in continuation: $eps") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 2240465be6..75790d040d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -76,7 +76,14 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly def analyze(vc: VariableContext): ProperPropertyComputationResult = { val vdScp = ps(VariableDefinition(vc.pc, vc.pv, vc.m), StringConstancyProperty.key) - continuation(ContextStringAnalysisState(vc, vdScp))(vdScp.asInstanceOf[SomeEPS]) + + implicit val state: ContextStringAnalysisState = ContextStringAnalysisState(vc, vdScp) + if (vdScp.isEPK) { + state._stringDependee = vdScp + computeResults + } else { + continuation(state)(vdScp.asInstanceOf[SomeEPS]) + } } private def continuation(state: ContextStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { @@ -111,6 +118,7 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly handleTACAI(m, tacai) computeResults + // "Upwards" dependency case EUBP(_: VariableContext, _: StringConstancyProperty) => state.updateParamDependee(eps.asInstanceOf[EOptionP[VariableContext, StringConstancyProperty]]) computeResults From a4f5cb447e9fe8d0dd8a64d2410a314605cda5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 19:41:36 +0200 Subject: [PATCH 455/583] Interpret arbitrary calls to object return types as well --- .../interpretation/L0StaticFunctionCallInterpreter.scala | 7 +++++-- .../interpretation/L0VirtualFunctionCallInterpreter.scala | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 1a18304a8f..a51b5984a1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -42,8 +42,11 @@ case class L0StaticFunctionCallInterpreter()( case "getProperty" if call.declaringClass == ObjectType("java/util/Properties") => System.out.println("TRACED STRING ANALYSIS FOR SYSTEM PROPERTY CALL!") interpretArbitraryCall(target, call) - case _ if call.descriptor.returnType == ObjectType.String => interpretArbitraryCall(target, call) - case _ => computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + case _ + if call.descriptor.returnType == ObjectType.String || + call.descriptor.returnType == ObjectType.Object => + interpretArbitraryCall(target, call) + case _ => computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 99d5ead4ba..1d64cea886 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -53,8 +53,6 @@ case class L0VirtualFunctionCallInterpreter() interpretSubstringCall(at, pt, call) case _ => call.descriptor.returnType match { - case obj: ObjectType if obj == ObjectType.String && at.isDefined => - interpretArbitraryCall(at.get, call) case _: IntLikeType if at.isDefined => computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, @@ -67,6 +65,8 @@ case class L0VirtualFunctionCallInterpreter() at.get, StringTreeDynamicFloat )) + case _ if at.isDefined => + interpretArbitraryCall(at.get, call) case _ => computeFinalResult(StringFlowFunctionProperty.identity) } From dbd7db0de8d24fb103a8eee45a4a148102983418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 22:06:13 +0200 Subject: [PATCH 456/583] Fix L1 tests by interpreting notable variable usages correctly --- .../string_analysis/l0/L0TestMethods.java | 2 +- .../string_analysis/l1/L1TestMethods.java | 131 ++++++++++++++---- .../properties/string/StringTreeNode.scala | 15 +- .../fpcf/analyses/string/StringAnalysis.scala | 3 +- .../analyses/string/StringAnalysisState.scala | 4 +- .../InterpretationHandler.scala | 5 +- .../L0InterpretationHandler.scala | 6 + .../L1InterpretationHandler.scala | 12 ++ .../string/StringFlowFunction.scala | 4 + 9 files changed, 143 insertions(+), 39 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 8762ec2b7e..e918687731 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -59,7 +59,7 @@ */ public class L0TestMethods { - private String someStringField = ""; + protected String someStringField = "private l0 non-final string field"; public static final String MY_CONSTANT = "mine"; /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 1d8cf48c85..abec6ad11e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -86,7 +86,7 @@ public void simpleNonVirtualFunctionCallTestWithIf(int i) { @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)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Runtime|ERROR|java.lang.StringBuilder)") }) public void initFromNonVirtualFunctionCallTest(int i) { String s; @@ -197,9 +197,7 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "get(.*|Hello, Worldjava.lang.Runtime)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" + expectedStrings = "(get.*|getHello, Worldjava.lang.Runtime)" ) }) public void dependenciesWithinFinalizeTest(String s, Class clazz) { @@ -268,9 +266,10 @@ public void noCallersInformationRequiredTest(String s) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "Hello, World_paintname((_PAD|_REFLECT|_REPEAT)?)?(_AlphaTest)?", - realisticLevel = DYNAMIC, - realisticStrings = ".*" + expectedStrings = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?", + realisticLevel = CONSTANT, + // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking + realisticStrings = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|Hello, World_paintname_AlphaTest|(Hello, World_paintname_PAD|Hello, World_paintname)_AlphaTest|Hello, World_paintname_REFLECT_AlphaTest|Hello, World_paintname_REPEAT_AlphaTest)" ) }) public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { @@ -400,13 +399,37 @@ private String getProperty(String name) { } @StringDefinitionsCollection( - value = "a case where a non virtual function has multiple return values", + value = "a case where the single valid return value of the called function can be resolved without calling the function", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(One|val|java.lang.Object)" + expectedLevel = CONSTANT, expectedStrings = "val", + // Since the virtual function return value is inlined before and its actual runtime return + // value is not used, the function call gets converted to a method call, which modifies the + // TAC: The def PC from the `analyzeString` parameter is now different and points to the def + // PC for the `resolvableReturnValueFunction` parameter. + realisticLevel = CONSTANT, realisticStrings = "" ) }) + public void resolvableReturnValue() { + analyzeString(resolvableReturnValueFunction("val", 42)); + } + + /** + * Belongs to resolvableReturnValue. + */ + private String resolvableReturnValueFunction(String s, int i) { + switch (i) { + case 0: return getObjectClassName(); + case 1: return "One"; + default: return s; + } + } + + @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)); } @@ -416,19 +439,16 @@ public void severalReturnValuesTest1() { */ private String severalReturnValuesFunction(String s, int i) { switch (i) { - case 0: return getObjectClassName(); - case 1: return "One"; - default: return s; + case 0: return "One"; + case 1: return s; + default: return getObjectClassName(); } } @StringDefinitionsCollection( value = "a case where a static function has multiple return values", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(that's odd|my.helper.Class)" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(that's odd|my.helper.Class)") }) public void severalReturnValuesTest2() { analyzeString(severalReturnValuesStaticFunction(42)); @@ -450,12 +470,10 @@ private static String severalReturnValuesStaticFunction(int i) { @StringDefinitionsCollection( value = "a case where a non-virtual and a static function have no return values at all", stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public void functionWithNoReturnValueTest1() { analyzeString(noReturnFunction1()); - analyzeString(noReturnFunction2()); } /** @@ -465,13 +483,6 @@ public String noReturnFunction1() { throw new RuntimeException(); } - /** - * Belongs to functionWithNoReturnValueTest1. - */ - public static String noReturnFunction2() { - throw new RuntimeException(); - } - @StringDefinitionsCollection( value = "a case where a static property is read", stringDefinitions = { @@ -497,6 +508,43 @@ public void getStringArrayField(int i) { // DIFFERING TEST CASES FROM PREVIOUS LEVELS + @StringDefinitionsCollection( + value = "a more comprehensive case where multiple definition sites have to be " + + "considered each with a different string generation mechanism", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "((java.lang.Object|java.lang.Runtime)|java.lang.System|java.lang.StringBuilder)", + realisticLevel = DYNAMIC, + // Array values are currently not interpreted. Also, differently constructed strings are + // currently not deduplicated since they result in different string trees during flow analysis. + realisticStrings = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)" + ) + }) + 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); + } + @StringDefinitionsCollection( value = "a test case which tests the interpretation of String#valueOf", stringDefinitions = { @@ -510,6 +558,18 @@ public void valueOfTest() { analyzeString(String.valueOf(getRuntimeClassName())); } + @StringDefinitionsCollection( + value = "an example that uses a non final field", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Field Value:private l0 non-final string field") + }) + public void nonFinalFieldRead() { + StringBuilder sb = new StringBuilder("Field Value:"); + System.out.println(sb); + sb.append(someStringField); + analyzeString(sb.toString()); + } + @StringDefinitionsCollection( value = "can handle virtual function calls", stringDefinitions = { @@ -520,6 +580,18 @@ public void fromFunctionCall() { analyzeString(className); } + @StringDefinitionsCollection( + value = "constant string + string from function call => CONSTANT", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") + }) + public void fromConstantAndFunctionCall() { + String className = "java.lang."; + System.out.println(className); + className += getSimpleStringBuilderClassName(); + analyzeString(className); + } + @StringDefinitionsCollection( value = "Some comprehensive example for experimental purposes taken from the JDK and slightly modified", stringDefinitions = { @@ -575,13 +647,16 @@ protected void setDebugFlags(String[] var1) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", - realisticLevel = DYNAMIC, realisticStrings = ".*" + // The real value is not fully resolved yet, since the string builder is used in a while loop, + // which leads to the string builder potentially carrying any value. This can be refined by + // recording pc specific states during data flow analysis. + realisticLevel = DYNAMIC, realisticStrings = "((iv1|iv2): |.*)" ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?", realisticLevel = DYNAMIC, - realisticStrings = ".*" + realisticStrings = "(.*|.*java.lang.Runtime)" ) }) public void extensive(boolean cond) { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 6ee199a5ed..fa8db10b2c 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -77,10 +77,17 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends override def simplify: StringTreeNode = { // TODO neutral concat something is always neutral val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) - if (nonNeutralChildren.isEmpty) - StringTreeNeutralElement - else - StringTreeConcat(nonNeutralChildren) + nonNeutralChildren.size match { + case 0 => StringTreeNeutralElement + case 1 => nonNeutralChildren.head + case _ => + var newChildren = Seq.empty[StringTreeNode] + nonNeutralChildren.foreach { + case concatChild: StringTreeConcat => newChildren :++= concatChild.children + case child => newChildren :+= child + } + StringTreeConcat(newChildren) + } } override def constancyLevel: StringConstancyLevel.Value = diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 75790d040d..83a06e205b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -61,7 +61,8 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec private def computeNewUpperBound(state: ContextFreeStringAnalysisState): StringConstancyProperty = { StringConstancyProperty(state.stringFlowDependee match { - case UBP(methodStringFlow) => StringConstancyInformation(methodStringFlow(state.entity.pc, state.entity.pv)) + case UBP(methodStringFlow) => + StringConstancyInformation(methodStringFlow(state.entity.pc, state.entity.pv).simplify) case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub }) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index ccf0b1a86f..dcad0deac7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -103,7 +103,7 @@ private[string] case class ContextStringAnalysisState( }.toMap } - StringConstancyInformation(stringTree.replaceParameters(paramTrees)) + StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) } else { StringConstancyInformation.ub } @@ -112,7 +112,7 @@ private[string] case class ContextStringAnalysisState( def finalSci: StringConstancyInformation = { if (_paramIndexToEntityMapping.valuesIterator.map(_.size).sum == 0) { val paramTrees = stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap - StringConstancyInformation(stringTree.replaceParameters(paramTrees)) + StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) } else { currentSciUB } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index f59372d17e..d3d326d27e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -50,9 +50,8 @@ abstract class InterpretationHandler extends FPCFAnalysis { private def continuation(state: InterpretationState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case finalEP: FinalEP[_, _] if - eps.pk.equals(TACAI.key) => - state.tacDependee = finalEP.asInstanceOf[FinalEP[Method, TACAI]] + case FinalEP(_, _) if eps.pk.equals(TACAI.key) => + state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] processStatementForState(state) case _ => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 2a4445ca3d..56f90492de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -57,6 +57,12 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case Assignment(_, target, _) => StringInterpreter.computeFinalLBFor(target) + case ReturnValue(pc, expr) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + pc, + expr.asVar.toPersistentForm(state.tac.stmts) + )) + case _ => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 4a6aa5b1b4..c0beb759ec 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -54,6 +54,12 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case ExprStmt(_, _: FieldRead[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case stmt: FieldWriteAccessStmt[V] => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + stmt.pc, + stmt.value.asVar.toPersistentForm(state.tac.stmts) + )) + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => @@ -84,6 +90,12 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case Assignment(_, target, _) => StringInterpreter.computeFinalLBFor(target) + case ReturnValue(pc, expr) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + pc, + expr.asVar.toPersistentForm(state.tac.stmts) + )) + case _ => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index f5f24334b2..0c20c7b602 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -48,6 +48,10 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat def identity: StringFlowFunctionProperty = StringFlowFunctionProperty(Set.empty[PDUWeb], IdentityFlow) + // Helps to register notable variable usage / definition which does not modify the current state + def identityForVariableAt(pc: Int, v: PV): StringFlowFunctionProperty = + StringFlowFunctionProperty(pc, v, IdentityFlow) + def ub(pc: Int, v: PV): StringFlowFunctionProperty = constForVariableAt(pc, v, StringTreeNeutralElement) From 600ef566d4431faf71e5226c6cd095638dcdacd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 22:07:38 +0200 Subject: [PATCH 457/583] Remove debug statements --- .../org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 83a06e205b..29db38082c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -137,16 +137,12 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly if (callerContext.hasContext && callerContext.method.hasSingleDefinedMethod) { val callerMethod = callerContext.method.definedMethod - System.out.println(s"FOUND RELEVANT CALLER FOR PC ${state.entity.pc} IN ${state.entity.m} FOR ${state.entity.pv}") - val tacEOptP = ps(callerMethod, TACAI.key) state.registerTacaiDepender(tacEOptP, (callerContext, pc)) if (tacEOptP.hasUBP) { handleTACAI(callerMethod, tacEOptP.ub) } - } else { - System.out.println(s"NON-SDM FOUND FOR PC ${state.entity.pc} IN ${state.entity.m} FOR ${state.entity.pv}") } } } From 2f4e0c6df518b7281c7af1edefa7788f9830f7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 16 Jun 2024 22:10:17 +0200 Subject: [PATCH 458/583] Enable a few more L1 tests that now pass --- .../src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 2 -- 1 file changed, 2 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 7ecdf6f64e..4c9e020cac 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 @@ -207,8 +207,6 @@ class L1StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) // Currently broken L1 Tests .filterNot(entity => entity.context.method.name.startsWith("cyclicDependencyTest")) - .filterNot(entity => entity.context.method.name.startsWith("unknownHierarchyInstanceTest")) - .filterNot(entity => entity.context.method.name.startsWith("crissCrossExample")) entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() From f7092edfc7ffbe9a612230a90a61484f68c16226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 17 Jun 2024 22:37:27 +0200 Subject: [PATCH 459/583] Refactor test overrides such that only annotations need to be specified but override hints by IDE persist --- .../string_analysis/l1/L1TestMethods.java | 115 ++---------------- .../org/opalj/fpcf/StringAnalysisTest.scala | 40 +++--- 2 files changed, 32 insertions(+), 123 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index abec6ad11e..28522df3f0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -521,29 +521,7 @@ public void getStringArrayField(int i) { realisticStrings = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)" ) }) - 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); - } + public void multipleDefSites(int value) {} @StringDefinitionsCollection( value = "a test case which tests the interpretation of String#valueOf", @@ -552,45 +530,28 @@ public void multipleDefSites(int value) { @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())); - } + public void valueOfTest() {} @StringDefinitionsCollection( value = "an example that uses a non final field", stringDefinitions = { @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Field Value:private l0 non-final string field") }) - public void nonFinalFieldRead() { - StringBuilder sb = new StringBuilder("Field Value:"); - System.out.println(sb); - sb.append(someStringField); - analyzeString(sb.toString()); - } + public void nonFinalFieldRead() {} @StringDefinitionsCollection( value = "can handle virtual function calls", stringDefinitions = { @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") }) - public void fromFunctionCall() { - String className = getStringBuilderClassName(); - analyzeString(className); - } + public void fromFunctionCall() {} @StringDefinitionsCollection( value = "constant string + string from function call => CONSTANT", stringDefinitions = { @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") }) - public void fromConstantAndFunctionCall() { - String className = "java.lang."; - System.out.println(className); - className += getSimpleStringBuilderClassName(); - analyzeString(className); - } + public void fromConstantAndFunctionCall() {} @StringDefinitionsCollection( value = "Some comprehensive example for experimental purposes taken from the JDK and slightly modified", @@ -602,45 +563,7 @@ public void fromConstantAndFunctionCall() { realisticStrings = ".*" ), }) - 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()); - } - } + protected void setDebugFlags(String[] var1) {} @StringDefinitionsCollection( value = "an extensive example with many control structures", @@ -659,31 +582,7 @@ protected void setDebugFlags(String[] var1) { realisticStrings = "(.*|.*java.lang.Runtime)" ) }) - public void extensive(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()); - } - - analyzeString(sb.toString()); - } + public void extensive(boolean cond) {} private String getRuntimeClassName() { return "java.lang.Runtime"; 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 4c9e020cac..13a4c214c5 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 @@ -45,8 +45,11 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { /** * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, * [[extractPUVars]] is called with their [[TACode]]. + * + * @return An [[Iterable]] containing the [[VariableContext]] to be analyzed and the method that has the relevant + * annotations attached. */ - def determineEntitiesToAnalyze(project: Project[URL]): Iterable[VariableContext] = { + def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(VariableContext, Method)] = { val tacProvider = project.get(EagerDetachedTACAIKey) val declaredMethods = project.get(DeclaredMethodsKey) val contextProvider = project.get(ContextProviderKey) @@ -56,17 +59,24 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { )(project).toList .filter(_.thisType.packageName.startsWith("org/opalj/fpcf/fixtures/string_analysis/")) .sortBy { cf => cf.thisType.simpleName.substring(1, 2).toInt } - .foldRight(Seq.empty[Method]) { (cf, methods) => - methods ++ cf.methods.filterNot(m => methods.exists(_.name == m.name)) + .foldLeft(Map.empty[Method, Method]) { (implementationsToAnnotations, cf) => + implementationsToAnnotations ++ cf.methods.map { m => + ( + implementationsToAnnotations.find(kv => + kv._1.name == m.name && kv._1.descriptor == m.descriptor + ).map(_._1).getOrElse(m), + m + ) + } } .filter { - _.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => + _._1.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } - .foldLeft(Seq.empty[VariableContext]) { (entities, m) => - entities ++ extractPUVars(tacProvider(m)).map(e => - VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m))) + .foldLeft(Seq.empty[(VariableContext, Method)]) { (entities, m) => + entities ++ extractPUVars(tacProvider(m._1)).map(e => + (VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m._1))), m._2) ) } } @@ -86,18 +96,18 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } def determineEAS( - entities: Iterable[VariableContext], + entities: Iterable[(VariableContext, Method)], project: Project[URL] ): Iterable[(VariableContext, String => String, List[Annotation])] = { - val m2e = entities.groupBy(_.context).iterator.map(e => - e._1.method.definedMethod -> (e._1, e._2.map(k => (k.pc, k.pv))) + val m2e = entities.groupBy(_._2).iterator.map(e => + e._1 -> (e._1, e._2.map(k => k._1)) ).toMap // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => m2e(am._1)._2.zipWithIndex.map { - case ((pc, puVar), index) => + case (vc, index) => Tuple3( - VariableContext(pc, puVar, m2e(am._1)._1), + vc, { s: String => s"${am._2(s)} (#$index)" }, List(StringAnalysisTest.getStringDefinitionsFromCollection(am._3, index)) ) @@ -168,7 +178,7 @@ class L0StringAnalysisTest extends StringAnalysisTest { val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) - entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() @@ -206,8 +216,8 @@ class L1StringAnalysisTest extends StringAnalysisTest { val entities = determineEntitiesToAnalyze(as.project) // Currently broken L1 Tests - .filterNot(entity => entity.context.method.name.startsWith("cyclicDependencyTest")) - entities.foreach(as.propertyStore.force(_, StringConstancyProperty.key)) + .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) + entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() From 3d93873330dd0a6d2f3d0d6e9ae4703c0422bb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 18 Jun 2024 21:48:24 +0200 Subject: [PATCH 460/583] Improve performance of natural loop interpretation --- .../fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 49f1a1fe93..77873dd079 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -154,7 +154,7 @@ object DataFlowAnalysis { } } } - sortedCurrentNodes = nextNodeEnvs.map(_._1).sortBy(_.outer) + sortedCurrentNodes = nextNodeEnvs.map(_._1).distinct.sortBy(_.outer) currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } } From ec553c1f0e732534d12ca04c4cb1a043027c9d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 18 Jun 2024 23:14:31 +0200 Subject: [PATCH 461/583] Add more L0 test cases --- .../string_analysis/l0/L0TestMethods.java | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index e918687731..3839f745be 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -34,7 +34,7 @@ * options (but definitely one of these values). * *
  • - * Brackets ("(" and "(") are used for nesting and grouping string expressions. + * Brackets ("(" and ")") are used for nesting and grouping string expressions. *
  • *
  • * The string "^-?\d+$" represents (positive and negative) integer numbers. This RegExp has been taken @@ -55,7 +55,7 @@ * 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 + * @author Maximilian Rüsch */ public class L0TestMethods { @@ -74,6 +74,9 @@ public class L0TestMethods { public void analyzeString(String s) { } + public void analyzeString(StringBuilder sb) { + } + @StringDefinitionsCollection( value = "read-only string variable, trivial case", stringDefinitions = { @@ -158,6 +161,39 @@ public void directAppendConcats() { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "tests support for passing a string builder into String.valueOf and directly as the analyzed string", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "SomeOther"), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "SomeOther", + realisticLevel = CONSTANT, + realisticStrings = "(Some|SomeOther)" + ) + }) + public void stringValueOfWithStringBuilder() { + StringBuilder sb = new StringBuilder("Some"); + sb.append("Other"); + analyzeString(String.valueOf(sb)); + + analyzeString(sb); + } + + @StringDefinitionsCollection( + value = "tests support for passing a string builder into String.valueOf and directly as the analyzed string", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Some"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Other") + }) + public void stringBuilderBufferInitArguments() { + StringBuilder sb = new StringBuilder("Some"); + analyzeString(sb.toString()); + + StringBuffer sb2 = new StringBuffer("Other"); + analyzeString(sb2.toString()); + } + @StringDefinitionsCollection( value = "at this point, function call cannot be handled => DYNAMIC", stringDefinitions = { @@ -250,6 +286,22 @@ public void appendWithTwoDefSites(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @StringDefinitionsCollection( + value = "a set of tests that test compatibility with ternary operators", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Some|SomeOther)"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(.*|Some)"), + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(SomeOther|Some.*)") + }) + public void ternaryOperators(boolean flag, String param) { + String s1 = "Some"; + String s2 = s1 + "Other"; + + analyzeString(flag ? s1 : s2); + analyzeString(flag ? s1 : param); + analyzeString(flag ? s1 + param : s2); + } + @StringDefinitionsCollection( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", From 38bfe45ea1421e9f452297641b015a75737880fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 19 Jun 2024 00:00:52 +0200 Subject: [PATCH 462/583] Refactor reflective calls support tool into a more readable form --- .../info/StringAnalysisReflectiveCalls.scala | 275 ++++++++---------- 1 file changed, 125 insertions(+), 150 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 359488d684..4432df9f82 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 @@ -10,6 +10,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.Method +import org.opalj.br.ObjectType import org.opalj.br.ReferenceType import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DeclaredMethods @@ -19,6 +20,7 @@ import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyLevel @@ -27,7 +29,7 @@ import org.opalj.br.instructions.Instruction import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.instructions.INVOKEVIRTUAL import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimELUBP +import org.opalj.fpcf.InterimEUBP import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -48,6 +50,7 @@ import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.util.PerformanceEvaluation.time /** * Analyzes a project for calls provided by the Java Reflection API and tries to determine which @@ -63,11 +66,20 @@ import org.opalj.tac.fpcf.properties.TACAI *
  • `Class.getDeclaredMethod(String, Class[])`
  • * * - * @author Patrick Mell + * @author Maximilian Rüsch */ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { - private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] + private case class Configuration( + includeCrypto: Boolean, + private val runL0Analysis: Boolean + ) { + + def analyses: Seq[FPCFLazyAnalysisScheduler] = { + if (runL0Analysis) LazyL0StringAnalysis.allRequiredAnalyses + else LazyL1StringAnalysis.allRequiredAnalyses + } + } private val relevantCryptoMethodNames = List( "javax.crypto.Cipher#getInstance", @@ -95,48 +107,35 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { "java.lang.Class#getDeclaredMethod" ) - private var includeCrypto = false - /** - * Retrieves all relevant method names, i.e., those methods from the Reflection API that have at least one string - * argument and shall be considered by this analysis. The string are supposed to have the format as produced - * by [[buildFQMethodName]]. If the 'crypto' parameter is set, relevant methods of the javax.crypto API are - * included, too. - */ - private def relevantMethodNames = if (includeCrypto) - relevantReflectionMethodNames ++ relevantCryptoMethodNames - else - relevantReflectionMethodNames - - /** - * 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]]. + * 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 entityContext = ListBuffer[(VariableContext, String)]() - private var declaredMethods: DeclaredMethods = _ - private var contextProvider: ContextProvider = _ - /** * 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() - // 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" override def description: String = { - "Finds calls to methods provided by the Java Reflection API and tries to resolve passed " + - "string values" + "Finds calls to methods provided by the Java Reflection API and tries to resolve passed string values" + } + + /** + * Retrieves all relevant method names, i.e., those methods from the Reflection API that have at least one string + * argument and shall be considered by this analysis. The string are supposed to have the format as produced + * by [[buildFQMethodName]]. If the 'crypto' parameter is set, relevant methods of the javax.crypto API are + * included, too. + */ + private def relevantMethodNames(implicit configuration: Configuration) = { + if (configuration.includeCrypto) + relevantReflectionMethodNames ++ relevantCryptoMethodNames + else + relevantReflectionMethodNames } /** @@ -144,8 +143,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * 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". */ - @inline - private final def buildFQMethodName(declaringClass: ReferenceType, methodName: String): String = + @inline private final def buildFQMethodName(declaringClass: ReferenceType, methodName: String): String = s"${declaringClass.toJava}#$methodName" /** @@ -155,30 +153,24 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * @note Internally, this method makes use of [[relevantMethodNames]]. A method can only be * relevant if it occurs in [[relevantMethodNames]]. */ - @inline - private final def isRelevantCall(declaringClass: ReferenceType, methodName: String): Boolean = - relevantMethodNames.contains(buildFQMethodName(declaringClass, methodName)) + @inline private final def isRelevantCall(declaringClass: ReferenceType, methodName: String)( + implicit configuration: Configuration + ): Boolean = relevantMethodNames.contains(buildFQMethodName(declaringClass, methodName)) /** * 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 = { - + private def instructionsContainRelevantMethod(instructions: Array[Instruction])( + implicit configuration: Configuration + ): Boolean = { instructions .filter(_ != null) - .foreach { instr => - (instr.opcode: @switch) match { - case INVOKESTATIC.opcode => - val INVOKESTATIC(declClass, _, methodName, _) = instr - if (isRelevantCall(declClass, methodName)) return true - case INVOKEVIRTUAL.opcode => - val INVOKEVIRTUAL(declClass, methodName, _) = instr - if (isRelevantCall(declClass, methodName)) return true - } + .exists { + case INVOKESTATIC(declClass, _, methodName, _) if isRelevantCall(declClass, methodName) => true + case INVOKEVIRTUAL(declClass, methodName, _) if isRelevantCall(declClass, methodName) => true + case _ => false } - - false } /** @@ -186,65 +178,33 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * `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( - pc: Int, - ps: PropertyStore, - method: Method, - call: Call[V], - resultMap: ResultMapType - )(implicit stmts: Array[Stmt[V]]): Unit = { + private def processFunctionCall(pc: Int, method: Method, call: Call[V])( + implicit + stmts: Array[Stmt[V]], + ps: PropertyStore, + contextProvider: ContextProvider, + declaredMethods: DeclaredMethods, + configuration: Configuration + ): Unit = { if (isRelevantCall(call.declaringClass, call.name)) { - val fqnMethodName = buildFQMethodName(method.classFile.thisType, method.name) - if (!ignoreMethods.contains(fqnMethodName)) { - 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 context = contextProvider.newContext(declaredMethods(method)) - val e = VariableContext(pc, call.params(index).asVar.toPersistentForm, context) - ps.force(e, StringConstancyProperty.key) - entityContext.append((e, buildFQMethodName(call.declaringClass, call.name))) - } - } - } - executionCounter += 1 - } - } - } - - /** - * 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 - } + // Loop through all parameters and start the analysis for those that take a string + call.descriptor.parameterTypes.zipWithIndex.foreach { + case (ft, index) if ft == ObjectType.String => + val context = contextProvider.newContext(declaredMethods(method)) + val e = VariableContext(pc, call.params(index).asVar.toPersistentForm, context) + ps.force(e, StringConstancyProperty.key) + entityContext.append((e, buildFQMethodName(call.declaringClass, call.name))) + case _ => } - - 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) } - private def processStatements( - ps: PropertyStore, - tac: TACode[TACMethodParameter, V], - m: Method, - resultMap: ResultMapType + private def processStatements(tac: TACode[TACMethodParameter, V], m: Method)( + implicit + contextProvider: ContextProvider, + ps: PropertyStore, + declaredMethods: DeclaredMethods, + configuration: Configuration ): Unit = { implicit val stmts: Array[Stmt[V]] = tac.stmts stmts.foreach { stmt => @@ -252,16 +212,16 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { (stmt.astID: @switch) match { case Assignment.ASTID => stmt match { case Assignment(pc, _, c: StaticFunctionCall[V]) => - processFunctionCall(pc, ps, m, c, resultMap) + processFunctionCall(pc, m, c) case Assignment(pc, _, c: VirtualFunctionCall[V]) => - processFunctionCall(pc, ps, m, c, resultMap) + processFunctionCall(pc, m, c) case _ => } case ExprStmt.ASTID => stmt match { case ExprStmt(pc, c: StaticFunctionCall[V]) => - processFunctionCall(pc, ps, m, c, resultMap) + processFunctionCall(pc, m, c) case ExprStmt(pc, c: VirtualFunctionCall[V]) => - processFunctionCall(pc, ps, m, c, resultMap) + processFunctionCall(pc, m, c) case _ => } case _ => @@ -269,14 +229,16 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } } - private def continuation( - ps: PropertyStore, - m: Method, - resultMap: ResultMapType - )(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(m: Method)(eps: SomeEPS)( + implicit + contextProvider: ContextProvider, + declaredMethods: DeclaredMethods, + ps: PropertyStore, + configuration: Configuration + ): ProperPropertyComputationResult = { eps match { case FinalP(tac: TACAI) => - processStatements(ps, tac.tac.get, m, resultMap) + processStatements(tac.tac.get, m) Result(m, tac) case InterimLUBP(lb, ub) => InterimResult( @@ -284,22 +246,20 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { lb, ub, Set(eps), - continuation(ps, m, resultMap) + continuation(m) ) case _ => throw new IllegalStateException("should never happen!") } } override def checkAnalysisSpecificParameters(parameters: Seq[String]): Seq[String] = { - parameters.flatMap { p => p.toLowerCase match { case "-includecryptoapi" => Nil - case "-intraprocedural" => Nil + case "-l0" => Nil case _ => List(s"Unknown parameter: $p") } } - } override def doAnalyze( @@ -307,36 +267,34 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { - - // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. - includeCrypto = parameters.exists(p => p.equalsIgnoreCase("-includeCryptoApi")) - // Check whether intraprocedural analysis should be run. By default, interprocedural is selected. - val runIntraproceduralAnalysis = parameters.exists(p => p.equalsIgnoreCase("-intraprocedural")) + implicit val configuration: Configuration = Configuration( + // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. + includeCrypto = parameters.exists(p => p.equalsIgnoreCase("-includeCryptoApi")), + // Check whether the L0 analysis should be run. By default, L1 is selected. + runL0Analysis = parameters.exists(p => p.equalsIgnoreCase("-l0")) + ) val manager = project.get(FPCFAnalysesManagerKey) project.get(RTACallGraphKey) - contextProvider = project.get(ContextProviderKey) - declaredMethods = project.get(DeclaredMethodsKey) - - implicit val (propertyStore, _) = manager.runAll( - if (runIntraproceduralAnalysis) LazyL0StringAnalysis.allRequiredAnalyses - else LazyL1StringAnalysis.allRequiredAnalyses - ) + implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) + implicit val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + implicit val (propertyStore, _) = manager.runAll(configuration.analyses) // Stores the obtained results for each supported reflective operation val resultMap = mutable.Map[String, ListBuffer[StringConstancyInformation]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } project.allMethodsWithBody.foreach { m => - // To dramatically reduce work, quickly check if a method is relevant at all - if (instructionsContainRelevantMethod(m.body.get.instructions)) { + val fqnMethodName = buildFQMethodName(m.classFile.thisType, m.name) + // To dramatically reduce work, quickly check if a method is ignored or not relevant at all + if (!ignoreMethods.contains(fqnMethodName) && instructionsContainRelevantMethod(m.body.get.instructions)) { 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 { - processStatements(propertyStore, tacaiEOptP.ub.tac.get, m, resultMap) + processStatements(tacaiEOptP.ub.tac.get, m) } } else { InterimResult( @@ -344,30 +302,47 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { StringConstancyProperty.ub, StringConstancyProperty.lb, Set(tacaiEOptP), - continuation(propertyStore, m, resultMap) + continuation(m) ) } } } - val t0 = System.currentTimeMillis() - propertyStore.waitOnPhaseCompletion() - entityContext.foreach { - case (e, callName) => - propertyStore.properties(e).toIndexedSeq.foreach { - 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!") - } - } + time { + propertyStore.waitOnPhaseCompletion() + entityContext.foreach { + case (e, callName) => + propertyStore.properties(e).toIndexedSeq.foreach { + case FinalP(p: StringConstancyProperty) => + resultMap(callName).append(p.stringConstancyInformation) + case InterimEUBP(_, ub: StringConstancyProperty) => + resultMap(callName).append(ub.stringConstancyInformation) + case _ => + println(s"No result for $e in $callName found!") + } + } + } { t => println(s"Elapsed Time: ${t.toMilliseconds} ms") } - val t1 = System.currentTimeMillis() - println(s"Elapsed Time: ${t1 - t0} ms") resultMapToReport(resultMap) } + private def resultMapToReport(resultMap: mutable.Map[String, ListBuffer[StringConstancyInformation]]): 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) + } } From 3f0649784643e2070dfc666e375bcd7d1a030f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 26 Jun 2024 22:57:06 +0200 Subject: [PATCH 463/583] Run tests with domains from both L1 and L2 --- .../string_analysis/l1/L1TestMethods.java | 6 +- .../string_analysis/AllowedDomainLevels.java | 15 ++ .../string_analysis/DomainLevel.java | 21 +++ .../org/opalj/fpcf/StringAnalysisTest.scala | 134 +++++++++++++----- 4 files changed, 137 insertions(+), 39 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 28522df3f0..d52a1c1877 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -4,6 +4,8 @@ import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.HelloGreeting; import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; +import org.opalj.fpcf.properties.string_analysis.AllowedDomainLevels; +import org.opalj.fpcf.properties.string_analysis.DomainLevel; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -398,12 +400,14 @@ private String getProperty(String name) { } } + // On L1, this test is equivalent to the `severalReturnValuesTest1` + @AllowedDomainLevels(DomainLevel.L2) @StringDefinitionsCollection( value = "a case where the single valid return value of the called function can be resolved without calling the function", stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "val", - // Since the virtual function return value is inlined before and its actual runtime return + // Since the virtual function return value is inlined in L2 and its actual runtime return // value is not used, the function call gets converted to a method call, which modifies the // TAC: The def PC from the `analyzeString` parameter is now different and points to the def // PC for the `resolvableReturnValueFunction` parameter. diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java new file mode 100644 index 0000000000..2f96bd8b83 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java @@ -0,0 +1,15 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import java.lang.annotation.*; + +/** + * @author Maximilian Rüsch + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface AllowedDomainLevels { + + DomainLevel[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java new file mode 100644 index 0000000000..62e1583ddb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +/** + * @author Maximilian Rüsch + */ +public enum DomainLevel { + + L1("L1"), + L2("L2"); + + private final String value; + + DomainLevel(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} 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 13a4c214c5..0a7f48c0c1 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 @@ -4,6 +4,7 @@ package fpcf import java.net.URL +import org.opalj.ai.domain.l1.DefaultDomainWithCFGAndDefUse import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse import org.opalj.ai.fpcf.properties.AIDomainFactoryKey import org.opalj.br.Annotation @@ -15,6 +16,7 @@ import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.fpcf.properties.string_analysis.DomainLevel import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV import org.opalj.tac.TACMethodParameter @@ -33,6 +35,24 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { val nameTestMethod: String = "analyzeString" def level: Int + def domainLevel: DomainLevel + + override final def init(p: Project[URL]): Unit = { + val domain = domainLevel match { + case DomainLevel.L1 => classOf[DefaultDomainWithCFGAndDefUse[_]] + case DomainLevel.L2 => classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(domain) + case Some(requirements) => requirements + domain + } + + initBeforeCallGraph(p) + + p.get(RTACallGraphKey) + } + + def initBeforeCallGraph(p: Project[URL]): Unit = {} override def fixtureProjectPackage: List[String] = { StringAnalysisTest.getFixtureProjectPackages(level).toList @@ -74,6 +94,12 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { exists || StringAnalysisTest.isStringUsageAnnotation(a) ) } + .filter { + _._1.runtimeInvisibleAnnotations.forall { a => + val r = isAllowedDomainLevel(a) + r.isEmpty || r.get + } + } .foldLeft(Seq.empty[(VariableContext, Method)]) { (entities, m) => entities ++ extractPUVars(tacProvider(m._1)).map(e => (VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m._1))), m._2) @@ -114,6 +140,15 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } } } + + def isAllowedDomainLevel(a: Annotation): Option[Boolean] = { + if (a.annotationType.toJava != "org.opalj.fpcf.properties.string_analysis.AllowedDomainLevels") None + else Some { + a.elementValuePairs.head.value.asArrayValue.values.exists { v => + DomainLevel.valueOf(v.asEnumValue.constName) == domainLevel + } + } + } } object StringAnalysisTest { @@ -138,7 +173,7 @@ object StringAnalysisTest { * @return True if the `a` is of type StringDefinitions and false otherwise. */ def isStringUsageAnnotation(a: Annotation): Boolean = - a.annotationType.toJavaClass.getName == "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" + a.annotationType.toJava == "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" /** * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. @@ -150,8 +185,16 @@ object StringAnalysisTest { * get. * @return Returns the desired `StringDefinitions` annotation. */ - def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = - a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation + def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = { + val collectionOpt = a.find(isStringUsageAnnotation) + if (collectionOpt.isEmpty) { + throw new IllegalArgumentException( + "Tried to collect string definitions from method that does not define them!" + ) + } + + collectionOpt.get.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation + } } /** @@ -160,31 +203,37 @@ object StringAnalysisTest { * * @author Maximilian Rüsch */ -class L0StringAnalysisTest extends StringAnalysisTest { +sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { - override def level = 0 + override final def level = 0 - override def init(p: Project[URL]): Unit = { - val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] - p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => Set(domain) - case Some(requirements) => requirements + domain - } + final def runL0Tests(): Unit = { + describe("the org.opalj.fpcf.L0StringAnalysis is started") { + val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) - p.get(RTACallGraphKey) + val entities = determineEntitiesToAnalyze(as.project) + entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) + + as.propertyStore.waitOnPhaseCompletion() + as.propertyStore.shutdown() + + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + } } +} + +class L0StringAnalysisWithL1DefaultDomainTest extends L0StringAnalysisTest { - describe("the org.opalj.fpcf.L0StringAnalysis is started") { - val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) + override def domainLevel: DomainLevel = DomainLevel.L1 - val entities = determineEntitiesToAnalyze(as.project) - entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) + describe("using the l1 default domain") { runL0Tests() } +} - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() +class L0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) - } + override def domainLevel: DomainLevel = DomainLevel.L2 + + describe("using the l2 default domain") { runL0Tests() } } /** @@ -193,35 +242,44 @@ class L0StringAnalysisTest extends StringAnalysisTest { * * @author Maximilian Rüsch */ -class L1StringAnalysisTest extends StringAnalysisTest { +sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { override def level = 1 - override def init(p: Project[URL]): Unit = { - val domain = classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] - p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => Set(domain) - case Some(requirements) => requirements + domain - } + override def initBeforeCallGraph(p: Project[URL]): Unit = { p.updateProjectInformationKeyInitializationData(FieldAccessInformationKey) { case None => Seq(EagerFieldAccessInformationAnalysis) case Some(requirements) => requirements :+ EagerFieldAccessInformationAnalysis } - - p.get(RTACallGraphKey) } - describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) + final def runL1Tests(): Unit = { + describe("the org.opalj.fpcf.L1StringAnalysis is started") { + val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) - val entities = determineEntitiesToAnalyze(as.project) - // Currently broken L1 Tests - .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) - entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) + val entities = determineEntitiesToAnalyze(as.project) + // Currently broken L1 Tests + .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) + entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() + as.propertyStore.waitOnPhaseCompletion() + as.propertyStore.shutdown() - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + } } } + +class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L1 + + describe("using the l1 default domain") { runL1Tests() } +} + +class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + + describe("using the l2 default domain") { runL1Tests() } +} From 721554b3f11809985e1a5efaae7583db2bd62f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 27 Jun 2024 21:31:45 +0200 Subject: [PATCH 464/583] Remove unused methods --- .../properties/string/StringConstancyInformation.scala | 1 - .../fpcf/properties/string/StringConstancyProperty.scala | 8 -------- 2 files changed, 9 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala index 71bb98f303..d63752bb4d 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala @@ -10,7 +10,6 @@ package string */ case class StringConstancyInformation(tree: StringTreeNode) { - final def isTheNeutralElement: Boolean = tree.isNeutralElement final def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel final def toRegex: String = tree.toRegex } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index f8dfd80782..30a4ae0952 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -29,14 +29,6 @@ class StringConstancyProperty( s"Level: $level, Possible Strings: ${stringConstancyInformation.toRegex}" } - /** - * @return Returns `true` if the [[stringConstancyInformation]] contained in this instance is - * the neutral element (see [[StringConstancyInformation.isTheNeutralElement]]). - */ - def isTheNeutralElement: Boolean = { - stringConstancyInformation.isTheNeutralElement - } - override def hashCode(): Int = stringConstancyInformation.hashCode() override def equals(o: Any): Boolean = o match { From 9102ff95653f316134357dc1509bdf7de603d2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 27 Jun 2024 22:28:30 +0200 Subject: [PATCH 465/583] Handle invalid flow instead of neutral elements --- .../string_analysis/l1/L1TestMethods.java | 5 +- .../string_analysis/StringConstancyLevel.java | 4 +- .../string_analysis/StringDefinitions.java | 1 + .../org/opalj/fpcf/StringAnalysisTest.scala | 1 + .../StringAnalysisMatcher.scala | 6 +- .../string/StringConstancyInformation.scala | 4 +- .../string/StringConstancyProperty.scala | 6 +- .../properties/string/StringTreeNode.scala | 83 ++++++++++--------- .../string/MethodStringFlowAnalysis.scala | 4 +- .../fpcf/analyses/string/StringAnalysis.scala | 3 +- .../analyses/string/StringAnalysisState.scala | 3 +- .../L0NonVirtualMethodCallInterpreter.scala | 7 +- .../L0VirtualMethodCallInterpreter.scala | 4 +- .../properties/string/MethodStringFlow.scala | 14 ++-- .../string/StringFlowFunction.scala | 9 +- 15 files changed, 84 insertions(+), 70 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index d52a1c1877..ba3d12d4d4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -410,8 +410,9 @@ private String getProperty(String name) { // Since the virtual function return value is inlined in L2 and its actual runtime return // value is not used, the function call gets converted to a method call, which modifies the // TAC: The def PC from the `analyzeString` parameter is now different and points to the def - // PC for the `resolvableReturnValueFunction` parameter. - realisticLevel = CONSTANT, realisticStrings = "" + // PC for the `resolvableReturnValueFunction` parameter. This results in no string flow being + // detected since the def and use sites are now inconsistent. + realisticLevel = INVALID, realisticStrings = StringDefinitions.INVALID_FLOW ) }) public void resolvableReturnValue() { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java index 6409ff4194..fb6c811138 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java @@ -14,7 +14,9 @@ public enum StringConstancyLevel { PARTIALLY_CONSTANT("partially_constant"), DYNAMIC("dynamic"), // Added to enable leaving a string constancy level of a string definition unspecified - UNSPECIFIED("unspecified"); + UNSPECIFIED("unspecified"), + // Added to enable specifying allowed invalid flow + INVALID("invalid"); private final String value; 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 bd7cae5dfb..0369859cec 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 @@ -26,6 +26,7 @@ public @interface StringDefinitions { String NO_STRINGS = "N/A"; + String INVALID_FLOW = ""; /** * This value determines the expected level of freedom for a local variable to 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 0a7f48c0c1..a895086ea0 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 @@ -258,6 +258,7 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) val entities = determineEntitiesToAnalyze(as.project) + .filter(entity => entity._2.name == "resolvableReturnValue") // Currently broken L1 Tests .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index b81b2dc99f..c37be71b99 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -72,7 +72,11 @@ class StringAnalysisMatcher extends AbstractPropertyMatcher { val (actLevel, actString) = properties.head match { case prop: StringConstancyProperty => val tree = prop.sci.tree.simplify - (tree.constancyLevel.toString.toLowerCase, tree.toRegex) + if (tree.isInvalid) { + (StringConstancyLevel.INVALID.toString.toLowerCase, StringDefinitions.INVALID_FLOW) + } else { + (tree.constancyLevel.toString.toLowerCase, tree.toRegex) + } case _ => ("", "") } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala index d63752bb4d..e169a11ab9 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala @@ -16,6 +16,6 @@ case class StringConstancyInformation(tree: StringTreeNode) { object StringConstancyInformation { - def lb: StringConstancyInformation = StringConstancyInformation(StringTreeDynamicString) - def ub: StringConstancyInformation = StringConstancyInformation(StringTreeNeutralElement) + def lb: StringConstancyInformation = StringConstancyInformation(StringTreeNode.lb) + def ub: StringConstancyInformation = StringConstancyInformation(StringTreeNode.ub) } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index 30a4ae0952..3c80d833a6 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -26,7 +26,11 @@ class StringConstancyProperty( override def toString: String = { val level = stringConstancyInformation.constancyLevel.toString.toLowerCase - s"Level: $level, Possible Strings: ${stringConstancyInformation.toRegex}" + val strings = if (stringConstancyInformation.tree.simplify.isInvalid) { + "No possible strings - Invalid Flow" + } else stringConstancyInformation.toRegex + + s"Level: $level, Possible Strings: $strings" } override def hashCode(): Int = stringConstancyInformation.hashCode() diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index fa8db10b2c..6d8819db36 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -25,19 +25,20 @@ sealed trait StringTreeNode { def collectParameterIndices: Set[Int] = children.flatMap(_.collectParameterIndices).toSet def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode - def isNeutralElement: Boolean = false - def isInvalidElement: Boolean = false + def isEmpty: Boolean = false + def isInvalid: Boolean = false } object StringTreeNode { def reduceMultiple(trees: Seq[StringTreeNode]): StringTreeNode = { if (trees.size == 1) trees.head - else if (trees.exists(_.isInvalidElement)) StringTreeInvalidElement + else if (trees.exists(_.isInvalid)) StringTreeInvalidElement else StringTreeOr(trees) } def lb: StringTreeNode = StringTreeDynamicString + def ub: StringTreeNode = StringTreeInvalidElement } case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { @@ -48,10 +49,10 @@ case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { override def simplify: StringTreeNode = { val simplifiedChild = child.simplify - if (simplifiedChild.isInvalidElement) + if (simplifiedChild.isInvalid) StringTreeInvalidElement - else if (simplifiedChild.isNeutralElement) - StringTreeNeutralElement + else if (simplifiedChild.isEmpty) + StringTreeEmptyConst else StringTreeRepetition(simplifiedChild) } @@ -68,25 +69,28 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends override def toRegex: String = { children.size match { - case 0 => StringTreeNeutralElement.toRegex + case 0 => throw new IllegalStateException("Tried to convert StringTreeConcat with no children to a regex!") case 1 => children.head.toRegex case _ => s"${children.map(_.toRegex).reduceLeft((o, n) => s"$o$n")}" } } override def simplify: StringTreeNode = { - // TODO neutral concat something is always neutral - val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) - nonNeutralChildren.size match { - case 0 => StringTreeNeutralElement - case 1 => nonNeutralChildren.head - case _ => - var newChildren = Seq.empty[StringTreeNode] - nonNeutralChildren.foreach { - case concatChild: StringTreeConcat => newChildren :++= concatChild.children - case child => newChildren :+= child - } - StringTreeConcat(newChildren) + val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty) + if (nonEmptyChildren.exists(_.isInvalid)) { + StringTreeInvalidElement + } else { + nonEmptyChildren.size match { + case 0 => StringTreeEmptyConst + case 1 => nonEmptyChildren.head + case _ => + var newChildren = Seq.empty[StringTreeNode] + nonEmptyChildren.foreach { + case concatChild: StringTreeConcat => newChildren :++= concatChild.children + case child => newChildren :+= child + } + StringTreeConcat(newChildren) + } } } @@ -98,7 +102,13 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } object StringTreeConcat { - def fromNodes(children: StringTreeNode*): StringTreeConcat = new StringTreeConcat(children) + def fromNodes(children: StringTreeNode*): StringTreeNode = { + if (children.isEmpty || children.exists(_.isInvalid)) { + StringTreeInvalidElement + } else { + new StringTreeConcat(children) + } + } } case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends StringTreeNode { @@ -112,13 +122,13 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } override def simplify: StringTreeNode = { - val nonNeutralChildren = children.map(_.simplify).filterNot(_.isNeutralElement) - nonNeutralChildren.size match { - case 0 => StringTreeNeutralElement - case 1 => nonNeutralChildren.head + val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty).filterNot(_.isInvalid) + nonEmptyChildren.size match { + case 0 => StringTreeInvalidElement + case 1 => nonEmptyChildren.head case _ => var newChildren = Seq.empty[StringTreeNode] - nonNeutralChildren.foreach { + nonEmptyChildren.foreach { case orChild: StringTreeOr => newChildren :++= orChild.children case child => newChildren :+= child } @@ -141,16 +151,16 @@ object StringTreeOr { def apply(children: Seq[StringTreeNode]): StringTreeNode = { if (children.isEmpty) { - StringTreeNeutralElement + StringTreeInvalidElement } else { new StringTreeOr(children) } } def fromNodes(children: StringTreeNode*): StringTreeNode = { - val nonNeutralDistinctChildren = children.distinct.filterNot(_.isNeutralElement) + val nonNeutralDistinctChildren = children.distinct.filterNot(_.isEmpty) nonNeutralDistinctChildren.size match { - case 0 => StringTreeNeutralElement + case 0 => StringTreeInvalidElement case 1 => nonNeutralDistinctChildren.head case _ => var newChildren = Seq.empty[StringTreeNode] @@ -183,7 +193,12 @@ case class StringTreeConst(string: String) extends SimpleStringTreeNode { def isIntConst: Boolean = Try(string.toInt).isSuccess - override def isNeutralElement: Boolean = string == "" + override def isEmpty: Boolean = string == "" +} + +object StringTreeEmptyConst extends StringTreeConst("") { + + override def isEmpty: Boolean = true } case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { @@ -208,20 +223,12 @@ object StringTreeParameter { } } -object StringTreeNeutralElement extends SimpleStringTreeNode { - override def toRegex: String = "" - - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT - - override def isNeutralElement: Boolean = true -} - object StringTreeInvalidElement extends SimpleStringTreeNode { override def toRegex: String = throw new UnsupportedOperationException() override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT - override def isInvalidElement: Boolean = true + override def isInvalid: Boolean = true } object StringTreeNull extends SimpleStringTreeNode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 991f8cefe6..7c26d7ac86 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -12,7 +12,7 @@ import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP @@ -116,7 +116,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => val defPCs = web.defPCs.toList.sorted if (defPCs.head >= 0) { - (web, StringTreeNeutralElement) + (web, StringTreeInvalidElement) } else { val pc = defPCs.head if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 29db38082c..b66ffa3790 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -63,7 +63,8 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec StringConstancyProperty(state.stringFlowDependee match { case UBP(methodStringFlow) => StringConstancyInformation(methodStringFlow(state.entity.pc, state.entity.pv).simplify) - case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub + case _: EPK[_, MethodStringFlow] => + StringConstancyInformation.ub }) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index dcad0deac7..172fdc686d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -13,7 +13,6 @@ import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.Entity @@ -97,7 +96,7 @@ private[string] case class ContextStringAnalysisState( .filter(_.hasUBP).map(_.ub.sci.tree).toSeq val paramTree = if (paramOptions.nonEmpty) StringTreeOr(paramOptions) - else StringTreeNeutralElement + else StringTreeNode.ub (index, paramTree.simplify) }.toMap diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala index ae4ae3fdf2..070b35423f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala @@ -7,6 +7,7 @@ package string package l0 package interpretation +import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment @@ -26,12 +27,12 @@ object L0NonVirtualMethodCallInterpreter extends StringInterpreter { } private def interpretInit(init: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { + val pc = state.pc + val targetVar = init.receiver.asVar.toPersistentForm(state.tac.stmts) init.params.size match { case 0 => - computeFinalResult(StringFlowFunctionProperty.identity) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt(pc, targetVar, StringTreeEmptyConst)) case _ => - val pc = state.pc - val targetVar = init.receiver.asVar.toPersistentForm(state.tac.stmts) // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters val paramVar = init.params.head.asVar.toPersistentForm(state.tac.stmts) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala index 1fa5860a06..7e460ea25e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala @@ -8,7 +8,7 @@ package l0 package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement +import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -36,7 +36,7 @@ object L0VirtualMethodCallInterpreter extends StringInterpreter { computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, pReceiver, - StringTreeNeutralElement + StringTreeEmptyConst )) case TheIntegerValue(intVal) => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala index 103a8b2cb1..330fd582e9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala @@ -5,8 +5,6 @@ package fpcf package properties package string -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey @@ -34,16 +32,16 @@ object MethodStringFlow extends MethodStringFlowPropertyMetaInformation { override val key: PropertyKey[MethodStringFlow] = PropertyKey.create(propertyName) - def ub: MethodStringFlow = AllNeutralMethodStringFlow - def lb: MethodStringFlow = AllDynamicMethodStringFlow + def ub: MethodStringFlow = AllUBMethodStringFlow + def lb: MethodStringFlow = AllLBMethodStringFlow } -object AllNeutralMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { +private object AllUBMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { - override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeNeutralElement + override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeNode.ub } -object AllDynamicMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { +private object AllLBMethodStringFlow extends MethodStringFlow(StringTreeEnvironment(Map.empty)) { - override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeDynamicString + override def apply(pc: Int, pv: PV): StringTreeNode = StringTreeNode.lb } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 0c20c7b602..2e578219ae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -5,9 +5,7 @@ package fpcf package properties package string -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement -import org.opalj.br.fpcf.properties.string.StringTreeNeutralElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey @@ -43,7 +41,7 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat StringFlowFunctionProperty(PDUWeb(pc, pv), flow) // TODO should this be the real bottom element? - def ub: StringFlowFunctionProperty = constForAll(StringTreeNeutralElement) + def ub: StringFlowFunctionProperty = constForAll(StringTreeInvalidElement) def identity: StringFlowFunctionProperty = StringFlowFunctionProperty(Set.empty[PDUWeb], IdentityFlow) @@ -52,11 +50,8 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat def identityForVariableAt(pc: Int, v: PV): StringFlowFunctionProperty = StringFlowFunctionProperty(pc, v, IdentityFlow) - def ub(pc: Int, v: PV): StringFlowFunctionProperty = - constForVariableAt(pc, v, StringTreeNeutralElement) - def lb(pc: Int, v: PV): StringFlowFunctionProperty = - constForVariableAt(pc, v, StringTreeDynamicString) + constForVariableAt(pc, v, StringTreeNode.lb) def noFlow(pc: Int, v: PV): StringFlowFunctionProperty = constForVariableAt(pc, v, StringTreeInvalidElement) From 8248100d0d7f28d66a21c3ad763f93643e5fbe14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 28 Jun 2024 18:08:20 +0200 Subject: [PATCH 466/583] Use field access information FPCF property instead of the project information key --- .../org/opalj/fpcf/StringAnalysisTest.scala | 3 +- .../analyses/string/l1/L1StringAnalysis.scala | 8 +- .../L1FieldReadInterpreter.scala | 123 ++++++++++++------ .../L1InterpretationHandler.scala | 11 +- 4 files changed, 95 insertions(+), 50 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 a895086ea0..df7b47d061 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,10 +255,9 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { final def runL1Tests(): Unit = { describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses) + val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses :+ EagerFieldAccessInformationAnalysis) val entities = determineEntitiesToAnalyze(as.project) - .filter(entity => entity._2.name == "resolvableReturnValue") // Currently broken L1 Tests .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index c2bee6754a..9283d78096 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -9,7 +9,9 @@ package l1 import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.properties.SystemProperties import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHandler @@ -35,7 +37,11 @@ object LazyL1StringAnalysis { object LazyL1StringFlowAnalysis extends LazyStringFlowAnalysis { - override final def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Callees)) ++ super.uses + override final def uses: Set[PropertyBounds] = super.uses ++ PropertyBounds.ubs( + Callees, + FieldWriteAccessInformation, + SystemProperties + ) override final def init(p: SomeProject, ps: PropertyStore): InitializationData = L1InterpretationHandler(p) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 1a1c7ee71e..7a78eb5778 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -7,10 +7,13 @@ package string package l1 package interpretation +import scala.collection.mutable.ListBuffer + +import org.opalj.br.DeclaredField import org.opalj.br.analyses.DeclaredFields -import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNode @@ -32,13 +35,12 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields - * via the [[FieldAccessInformation]]. + * via the [[FieldWriteAccessInformation]]. * * @author Maximilian Rüsch */ case class L1FieldReadInterpreter( ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider @@ -72,11 +74,15 @@ case class L1FieldReadInterpreter( } private case class FieldReadState( - target: PV, - var hasWriteInSameMethod: Boolean = false, - var hasInit: Boolean = false, - var hasUnresolvableAccess: Boolean = false, - var accessDependees: Seq[EOptionP[VariableDefinition, StringConstancyProperty]] = Seq.empty + target: PV, + var fieldAccessDependee: EOptionP[DeclaredField, FieldWriteAccessInformation], + var seenDirectFieldAccesses: Int = 0, + var seenIndirectFieldAccesses: Int = 0, + var hasWriteInSameMethod: Boolean = false, + var hasInit: Boolean = false, + var hasUnresolvableAccess: Boolean = false, + var accessDependees: Seq[EOptionP[VariableDefinition, StringConstancyProperty]] = Seq.empty, + previousResults: ListBuffer[StringTreeNode] = ListBuffer.empty ) { def updateAccessDependee(newDependee: EOptionP[VariableDefinition, StringConstancyProperty]): Unit = { @@ -86,9 +92,9 @@ case class L1FieldReadInterpreter( ) } - def hasDependees: Boolean = accessDependees.exists(_.isRefinable) + def hasDependees: Boolean = fieldAccessDependee.isRefinable || accessDependees.exists(_.isRefinable) - def dependees: Iterable[SomeEOptionP] = accessDependees.filter(_.isRefinable) + def dependees: Iterable[SomeEOptionP] = fieldAccessDependee +: accessDependees.filter(_.isRefinable) } /** @@ -105,24 +111,46 @@ case class L1FieldReadInterpreter( return computeFinalLBFor(target) } - val definedField = - declaredFields(fieldRead.declaringClass, fieldRead.name, fieldRead.declaredFieldType).asDefinedField - val writeAccesses = fieldAccessInformation.writeAccesses(definedField.definedField).toSeq - if (writeAccesses.length > fieldWriteThreshold) { - return computeFinalLBFor(target) + val field = declaredFields(fieldRead.declaringClass, fieldRead.name, fieldRead.declaredFieldType) + val fieldAccessEOptP = ps(field, FieldWriteAccessInformation.key) + + implicit val accessState: FieldReadState = FieldReadState(target, fieldAccessEOptP) + if (fieldAccessEOptP.hasUBP) { + handleFieldAccessInformation(fieldAccessEOptP.ub) + } else { + accessState.previousResults.prepend(StringTreeNode.ub) + InterimResult.forUB( + InterpretationHandler.getEntity, + StringFlowFunctionProperty.ub, + accessState.dependees.toSet, + continuation(accessState, state) + ) } - if (writeAccesses.isEmpty) { + tryComputeFinalResult + } + + private def handleFieldAccessInformation(accessInformation: FieldWriteAccessInformation)( + implicit + accessState: FieldReadState, + state: InterpretationState + ): ProperPropertyComputationResult = { + if (accessInformation.accesses.length > fieldWriteThreshold) { + return computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) + } + + if (accessState.fieldAccessDependee.isFinal && accessInformation.accesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value - return computeFinalResult(StringFlowFunctionProperty.constForVariableAt( - state.pc, - target, - StringTreeOr.fromNodes(StringTreeNull, StringTreeDynamicString) - )) + return computeFinalResult(computeUBWithNewTree(StringTreeOr.fromNodes( + StringTreeDynamicString, + StringTreeNull + ))) } - implicit val accessState: FieldReadState = FieldReadState(target) - writeAccesses.foreach { + accessInformation.getNewestAccesses( + accessInformation.numDirectAccesses - accessState.seenDirectFieldAccesses, + accessInformation.numIndirectAccesses - accessState.seenIndirectFieldAccesses + ).foreach { case (contextId, pc, _, parameter) => val method = contextProvider.contextFromId(contextId).method.definedMethod @@ -143,6 +171,9 @@ case class L1FieldReadInterpreter( } } + accessState.seenDirectFieldAccesses = accessInformation.numDirectAccesses + accessState.seenIndirectFieldAccesses = accessInformation.numIndirectAccesses + tryComputeFinalResult } @@ -153,20 +184,13 @@ case class L1FieldReadInterpreter( if (accessState.hasWriteInSameMethod) { // We cannot handle writes to a field that is read in the same method at the moment as the flow functions do // not capture field state. This can be improved upon in the future. - computeFinalLBFor(accessState.target) - } else if (accessState.hasDependees) { - InterimResult.forUB( - InterpretationHandler.getEntity, - StringFlowFunctionProperty.ub, - accessState.dependees.toSet, - continuation(accessState, state) - ) + computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) } else { - var trees = accessState.accessDependees.map(_.asFinal.p.sci.tree) + var trees = accessState.accessDependees.map(_.ub.sci.tree) // 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 (!accessState.hasInit) { + if (accessState.fieldAccessDependee.isFinal && !accessState.hasInit) { trees = trees :+ StringTreeNull } // If an access could not be resolved, append a dynamic element @@ -174,11 +198,16 @@ case class L1FieldReadInterpreter( trees = trees :+ StringTreeNode.lb } - computeFinalResult(StringFlowFunctionProperty.constForVariableAt( - state.pc, - accessState.target, - StringTreeNode.reduceMultiple(trees) - )) + if (accessState.hasDependees) { + InterimResult.forUB( + InterpretationHandler.getEntity, + computeUBWithNewTree(StringTreeNode.reduceMultiple(trees)), + accessState.dependees.toSet, + continuation(accessState, state) + ) + } else { + computeFinalResult(computeUBWithNewTree(StringTreeNode.reduceMultiple(trees))) + } } } @@ -187,6 +216,10 @@ case class L1FieldReadInterpreter( state: InterpretationState )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { + case UBP(ub: FieldWriteAccessInformation) => + accessState.fieldAccessDependee = eps.asInstanceOf[EOptionP[DeclaredField, FieldWriteAccessInformation]] + handleFieldAccessInformation(ub)(accessState, state) + case UBP(_: StringConstancyProperty) => accessState.updateAccessDependee(eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]]) tryComputeFinalResult(accessState, state) @@ -194,4 +227,18 @@ case class L1FieldReadInterpreter( case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } } + + private def computeUBWithNewTree(newTree: StringTreeNode)( + implicit + accessState: FieldReadState, + state: InterpretationState + ): StringFlowFunctionProperty = { + accessState.previousResults.prepend(newTree) + + StringFlowFunctionProperty.constForVariableAt( + state.pc, + accessState.target, + StringTreeOr(accessState.previousResults.toSeq) + ) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index c0beb759ec..0896cb07c5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -9,8 +9,6 @@ package interpretation import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.DeclaredFieldsKey -import org.opalj.br.analyses.FieldAccessInformation -import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey @@ -33,7 +31,6 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty class L1InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) - val fieldAccessInformation: FieldAccessInformation = p.get(FieldAccessInformationKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) override protected def processStatement(implicit @@ -46,10 +43,7 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) case stmt @ Assignment(_, _, expr: FieldRead[V]) => - L1FieldReadInterpreter(ps, fieldAccessInformation, p, declaredFields, contextProvider).interpretExpr( - stmt, - expr - ) + L1FieldReadInterpreter(ps, p, declaredFields, contextProvider).interpretExpr(stmt, expr) // Field reads without result usage are irrelevant case ExprStmt(_, _: FieldRead[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) @@ -103,8 +97,7 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend object L1InterpretationHandler { - def requiredProjectInformation: ProjectInformationKeys = - Seq(DeclaredFieldsKey, FieldAccessInformationKey, ContextProviderKey) + def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, ContextProviderKey) def apply(project: SomeProject): L1InterpretationHandler = new L1InterpretationHandler()(project) } From fb07cb87de17a5103f97aa411da5bba362f1f0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 13:34:21 +0200 Subject: [PATCH 467/583] Simplify or-nodes into single child if present --- .../org/opalj/br/fpcf/properties/string/StringTreeNode.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 6d8819db36..5f059963e4 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -152,6 +152,8 @@ object StringTreeOr { def apply(children: Seq[StringTreeNode]): StringTreeNode = { if (children.isEmpty) { StringTreeInvalidElement + } else if (children.take(2).size == 1) { + children.head } else { new StringTreeOr(children) } From 6c07b233db6a42997f902327c74917c7ca23639a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 13:34:29 +0200 Subject: [PATCH 468/583] Remove superflouous comment --- .../opalj/tac/fpcf/properties/string/StringFlowFunction.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 2e578219ae..119a2c20aa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -40,7 +40,6 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat def apply(pc: Int, pv: PV, flow: StringFlowFunction): StringFlowFunctionProperty = StringFlowFunctionProperty(PDUWeb(pc, pv), flow) - // TODO should this be the real bottom element? def ub: StringFlowFunctionProperty = constForAll(StringTreeInvalidElement) def identity: StringFlowFunctionProperty = From 7be416a96fbf200a750fa60698fd1a84f69c7d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 13:36:50 +0200 Subject: [PATCH 469/583] Fix handling of field reads for EPK access dependees --- .../l1/interpretation/L1FieldReadInterpreter.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 7a78eb5778..23e9a399ae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -117,6 +117,8 @@ case class L1FieldReadInterpreter( implicit val accessState: FieldReadState = FieldReadState(target, fieldAccessEOptP) if (fieldAccessEOptP.hasUBP) { handleFieldAccessInformation(fieldAccessEOptP.ub) + + tryComputeFinalResult } else { accessState.previousResults.prepend(StringTreeNode.ub) InterimResult.forUB( @@ -126,8 +128,6 @@ case class L1FieldReadInterpreter( continuation(accessState, state) ) } - - tryComputeFinalResult } private def handleFieldAccessInformation(accessInformation: FieldWriteAccessInformation)( @@ -186,7 +186,10 @@ case class L1FieldReadInterpreter( // not capture field state. This can be improved upon in the future. computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) } else { - var trees = accessState.accessDependees.map(_.ub.sci.tree) + var trees = accessState.accessDependees.map { ad => + if (ad.hasUBP) ad.ub.sci.tree + else StringTreeNode.ub + } // 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 From b68d36621fdba5eac1a89117c05ca56499770cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 13:38:27 +0200 Subject: [PATCH 470/583] Harden context string analysis against missing parameters --- .../fpcf/analyses/string/StringAnalysis.scala | 22 +++++++++++++----- .../analyses/string/StringAnalysisState.scala | 23 ++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index b66ffa3790..3b666e1096 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -23,6 +23,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP +import org.opalj.log.OPALLogger import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow @@ -166,12 +167,21 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly for { index <- state.stringTree.collectParameterIndices } { - val paramVC = VariableContext( - pc, - callExpr.params(index).asVar.toPersistentForm(tac.stmts), - callerContext - ) - state.registerParameterDependee(index, m, ps(paramVC, StringConstancyProperty.key)) + if (index >= callExpr.params.size) { + OPALLogger.warn( + "string analysis", + s"Found parameter reference $index with insufficient parameters during analysis of call: " + + s"${state.entity.m.toJava} in method ${m.toJava}" + ) + state.registerInvalidParamReference(index, m) + } else { + val paramVC = VariableContext( + pc, + callExpr.params(index).asVar.toPersistentForm(tac.stmts), + callerContext + ) + state.registerParameterDependee(index, m, ps(paramVC, StringConstancyProperty.key)) + } } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 172fdc686d..92de87d3fe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -61,6 +61,7 @@ private[string] case class ContextStringAnalysisState( mutable.Map.empty.withDefaultValue(mutable.Map.empty) private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = mutable.Map.empty + private val _invalidParamReferences: mutable.Map[Method, Set[Int]] = mutable.Map.empty def registerParameterDependee( index: Int, @@ -84,6 +85,9 @@ private[string] case class ContextStringAnalysisState( _paramDependees.remove(dependee.e) } } + def registerInvalidParamReference(index: Int, m: Method): Unit = { + _invalidParamReferences.updateWith(m)(ov => Some(ov.getOrElse(Set.empty) + index)) + } def currentSciUB: StringConstancyInformation = { if (_stringDependee.hasUBP) { @@ -91,14 +95,17 @@ private[string] case class ContextStringAnalysisState( stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap } else { stringTree.collectParameterIndices.map { index => - val paramOptions = _paramIndexToEntityMapping(index) - .valuesIterator.map(_paramDependees) - .filter(_.hasUBP).map(_.ub.sci.tree).toSeq - - val paramTree = if (paramOptions.nonEmpty) StringTreeOr(paramOptions) - else StringTreeNode.ub - - (index, paramTree.simplify) + val paramOptions = _paramIndexToEntityMapping(index).keysIterator.flatMap { m => + if (_invalidParamReferences.contains(m)) { + Some(StringTreeNode.ub) + } else { + val dependee = _paramDependees(_paramIndexToEntityMapping(index)(m)) + if (dependee.hasUBP) Some(dependee.ub.sci.tree) + else None + } + }.toSeq + + (index, StringTreeOr(paramOptions).simplify) }.toMap } From 0e570dda612ec2f84484d59ecef22f9f446ea5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 14:35:15 +0200 Subject: [PATCH 471/583] Remove unused functions --- .../string/interpretation/InterpretationHandler.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index d3d326d27e..c2cf1a44cb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -6,7 +6,6 @@ package analyses package string package interpretation -import org.opalj.br.DefinedMethod import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.fpcf.FPCFAnalysis @@ -80,11 +79,7 @@ abstract class InterpretationHandler extends FPCFAnalysis { object InterpretationHandler { - def getEntity(implicit state: InterpretationState): MethodPC = getEntity(state.pc, state.dm) - - def getEntity(pc: Int)(implicit state: InterpretationState): MethodPC = getEntity(pc, state.dm) - - def getEntity(pc: Int, dm: DefinedMethod): MethodPC = MethodPC(pc, dm) + def getEntity(implicit state: InterpretationState): MethodPC = MethodPC(state.pc, state.dm) /** * This function checks whether a given type is a supported primitive type. Supported currently From 6caa4ba2ed9d51ab6fe71844890a27a0e3bcb1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 14:38:40 +0200 Subject: [PATCH 472/583] Fix dependee collection of field read interpretation --- .../string/l1/interpretation/L1FieldReadInterpreter.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 23e9a399ae..a35bd8129a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -94,7 +94,12 @@ case class L1FieldReadInterpreter( def hasDependees: Boolean = fieldAccessDependee.isRefinable || accessDependees.exists(_.isRefinable) - def dependees: Iterable[SomeEOptionP] = fieldAccessDependee +: accessDependees.filter(_.isRefinable) + def dependees: Iterable[SomeEOptionP] = { + val dependees = accessDependees.filter(_.isRefinable) + + if (fieldAccessDependee.isRefinable) fieldAccessDependee +: dependees + else dependees + } } /** From a5e8c914c9dde85492a3c861805997b95bb766f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 14:39:03 +0200 Subject: [PATCH 473/583] Fix some L1 tests --- .../opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index ba3d12d4d4..a330b01b2e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -110,7 +110,7 @@ public void initFromNonVirtualFunctionCallTest(int i) { @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), }) public void staticMethodOutOfScopeTest() { - analyzeString(System.getProperty("os.version")); + analyzeString(System.clearProperty("os.version")); } @StringDefinitionsCollection( @@ -146,7 +146,7 @@ public void appendTest() { StringBuilder sb = new StringBuilder("classname:"); sb.append(getSimpleStringBuilderClassName()); sb.append(",osname:"); - sb.append(System.getProperty("os.name:")); + sb.append(System.clearProperty("os.name:")); analyzeString(sb.toString()); } From 6193e55516c84a4b79c9edf7c71dc20c114f72a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 16:24:36 +0200 Subject: [PATCH 474/583] Properly react to updates in function call interpretation --- .../string_analysis/l1/L1TestMethods.java | 2 +- .../properties/string/StringTreeNode.scala | 6 - .../L0FunctionCallInterpreter.scala | 119 ++++++++++-------- .../L0NonVirtualFunctionCallInterpreter.scala | 5 +- .../L0StaticFunctionCallInterpreter.scala | 12 +- .../L1FieldReadInterpreter.scala | 4 +- .../L1VirtualFunctionCallInterpreter.scala | 111 +++++++++------- 7 files changed, 139 insertions(+), 120 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index a330b01b2e..64bada5147 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -475,7 +475,7 @@ private static String severalReturnValuesStaticFunction(int i) { @StringDefinitionsCollection( value = "a case where a non-virtual and a static function have no return values at all", stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") + @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) }) public void functionWithNoReturnValueTest1() { analyzeString(noReturnFunction1()); diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 5f059963e4..33fd47b4d6 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -31,12 +31,6 @@ sealed trait StringTreeNode { object StringTreeNode { - def reduceMultiple(trees: Seq[StringTreeNode]): StringTreeNode = { - if (trees.size == 1) trees.head - else if (trees.exists(_.isInvalid)) StringTreeInvalidElement - else StringTreeOr(trees) - } - def lb: StringTreeNode = StringTreeDynamicString def ub: StringTreeNode = StringTreeInvalidElement } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index df063ddd8f..a0416336c9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.Method import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP import org.opalj.fpcf.InterimResult @@ -34,18 +35,22 @@ trait L0FunctionCallInterpreter implicit val ps: PropertyStore - protected[this] case class FunctionCallState( - state: InterpretationState, - target: PV, - calleeMethods: Seq[Method], - parameters: Seq[PV], - var tacDependees: Map[Method, EOptionP[Method, TACAI]], + type CallState <: FunctionCallState + + protected[this] class FunctionCallState( + val target: PV, + val parameters: Seq[PV], + var calleeMethods: Seq[Method] = Seq.empty, + var tacDependees: Map[Method, EOptionP[Method, TACAI]] = Map.empty, var returnDependees: Map[Method, Seq[EOptionP[VariableDefinition, StringConstancyProperty]]] = Map.empty ) { - def pc: Int = state.pc - var hasUnresolvableReturnValue: Map[Method, Boolean] = Map.empty.withDefaultValue(false) + def addCalledMethod(m: Method, tacDependee: EOptionP[Method, TACAI]): Unit = { + calleeMethods = calleeMethods :+ m + tacDependees = tacDependees.updated(m, tacDependee) + } + def updateReturnDependee( method: Method, newDependee: EOptionP[VariableDefinition, StringConstancyProperty] @@ -71,7 +76,8 @@ trait L0FunctionCallInterpreter } protected def interpretArbitraryCallToFunctions(implicit - callState: FunctionCallState + state: InterpretationState, + callState: CallState ): ProperPropertyComputationResult = { callState.calleeMethods.foreach { m => val tacEOptP = callState.tacDependees(m) @@ -82,19 +88,13 @@ trait L0FunctionCallInterpreter callState.hasUnresolvableReturnValue += m -> true } else { val returns = calleeTac.get.stmts.toIndexedSeq.filter(stmt => stmt.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 - callState.hasUnresolvableReturnValue += m -> true - } else { - callState.returnDependees += m -> returns.map { ret => - val entity = VariableDefinition( - ret.pc, - ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), - m - ) - ps(entity, StringConstancyProperty.key) - } + callState.returnDependees += m -> returns.map { ret => + val entity = VariableDefinition( + ret.pc, + ret.asInstanceOf[ReturnValue[V]].expr.asVar.toPersistentForm(calleeTac.get.stmts), + m + ) + ps(entity, StringConstancyProperty.key) } } } @@ -103,54 +103,63 @@ trait L0FunctionCallInterpreter tryComputeFinalResult } - private def tryComputeFinalResult(implicit callState: FunctionCallState): ProperPropertyComputationResult = { + private def tryComputeFinalResult( + implicit + state: InterpretationState, + callState: CallState + ): ProperPropertyComputationResult = { + val pc = state.pc + val parameters = callState.parameters.zipWithIndex.map(x => (x._2, x._1)).toMap + + val flowFunction: StringFlowFunction = (env: StringTreeEnvironment) => + env.update( + pc, + callState.target, + StringTreeOr { + callState.calleeMethods.map { m => + if (callState.hasUnresolvableReturnValue(m)) { + StringTreeNode.lb + } else { + StringTreeOr(callState.returnDependees(m).map { rd => + if (rd.hasUBP) { + rd.ub.sci.tree.replaceParameters(parameters.map { kv => (kv._1, env(pc, kv._2)) }) + } else StringTreeNode.ub + }) + } + } + } + ) + + val newUB = StringFlowFunctionProperty( + callState.parameters.map(PDUWeb(pc, _)).toSet + PDUWeb(pc, callState.target), + flowFunction + ) + if (callState.hasDependees) { InterimResult.forUB( - InterpretationHandler.getEntity(callState.state), - StringFlowFunctionProperty.ub, + InterpretationHandler.getEntity(state), + newUB, callState.dependees.toSet, - continuation(callState) + continuation(state, callState) ) } else { - val pc = callState.state.pc - val parameters = callState.parameters.zipWithIndex.map(x => (x._2, x._1)).toMap - - val flowFunction: StringFlowFunction = (env: StringTreeEnvironment) => - env.update( - pc, - callState.target, - StringTreeNode.reduceMultiple { - callState.calleeMethods.map { m => - if (callState.hasUnresolvableReturnValue(m)) { - StringTreeNode.lb - } else { - StringTreeNode.reduceMultiple(callState.returnDependees(m).map { - _.asFinal.p.sci.tree.replaceParameters(parameters.map { kv => - (kv._1, env(pc, kv._2)) - }) - }) - } - } - } - ) - - computeFinalResult( - callState.parameters.map(PDUWeb(pc, _)).toSet + PDUWeb(pc, callState.target), - flowFunction - )(callState.state) + computeFinalResult(newUB) } } - private def continuation(callState: FunctionCallState)(eps: SomeEPS): ProperPropertyComputationResult = { + protected[this] def continuation( + state: InterpretationState, + callState: CallState + )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case EUBP(m: Method, _: TACAI) => callState.tacDependees += m -> eps.asInstanceOf[EOptionP[Method, TACAI]] - interpretArbitraryCallToFunctions(callState) + interpretArbitraryCallToFunctions(state, callState) case EUBP(_, _: StringConstancyProperty) => val contextEPS = eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]] callState.updateReturnDependee(contextEPS.e.m, contextEPS) - tryComputeFinalResult(callState) + tryComputeFinalResult(state, callState) case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 149624b694..07591c9a2d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -25,6 +25,7 @@ case class L0NonVirtualFunctionCallInterpreter()( override type T = AssignmentLikeStmt[V] override type E = NonVirtualFunctionCall[V] + override type CallState = FunctionCallState override def interpretExpr(instr: T, expr: E)(implicit state: InterpretationState @@ -37,8 +38,8 @@ case class L0NonVirtualFunctionCallInterpreter()( val m = calleeMethod.value val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) - val callState = FunctionCallState(state, target, Seq(m), params, Map((m, ps(m, TACAI.key)))) + val callState = new FunctionCallState(target, params, Seq(m), Map((m, ps(m, TACAI.key)))) - interpretArbitraryCallToFunctions(callState) + interpretArbitraryCallToFunctions(state, callState) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index a51b5984a1..84a198107a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -33,15 +33,8 @@ case class L0StaticFunctionCallInterpreter()( override def interpretExpr(target: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - if (call.name == "executePrivileged") { - System.out.println("FOUND A PRIVILEDGED EXECUTION!") - } - call.name match { case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) - case "getProperty" if call.declaringClass == ObjectType("java/util/Properties") => - System.out.println("TRACED STRING ANALYSIS FOR SYSTEM PROPERTY CALL!") - interpretArbitraryCall(target, call) case _ if call.descriptor.returnType == ObjectType.String || call.descriptor.returnType == ObjectType.Object => @@ -58,6 +51,7 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter implicit val p: SomeProject override type E <: StaticFunctionCall[V] + override type CallState = FunctionCallState def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState @@ -69,9 +63,9 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter val m = calleeMethod.value val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) - val callState = FunctionCallState(state, target, Seq(m), params, Map((m, ps(m, TACAI.key)))) + val callState = new FunctionCallState(target, params, Seq(m), Map((m, ps(m, TACAI.key)))) - interpretArbitraryCallToFunctions(callState) + interpretArbitraryCallToFunctions(state, callState) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index a35bd8129a..02c2ae8fe0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -209,12 +209,12 @@ case class L1FieldReadInterpreter( if (accessState.hasDependees) { InterimResult.forUB( InterpretationHandler.getEntity, - computeUBWithNewTree(StringTreeNode.reduceMultiple(trees)), + computeUBWithNewTree(StringTreeOr(trees)), accessState.dependees.toSet, continuation(accessState, state) ) } else { - computeFinalResult(computeUBWithNewTree(StringTreeNode.reduceMultiple(trees))) + computeFinalResult(computeUBWithNewTree(StringTreeOr(trees))) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 698251cbc1..2ba91c9acc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -7,18 +7,16 @@ package string package l1 package interpretation -import scala.collection.mutable.ListBuffer - import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler @@ -34,70 +32,93 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter( - override implicit val ps: PropertyStore, + implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider ) extends L0VirtualFunctionCallInterpreter with StringInterpreter - with L0FunctionCallInterpreter { + with L1ArbitraryVirtualFunctionCallInterpreter { override type E = VirtualFunctionCall[V] - private case class CalleeDepender( - target: PV, - methodContext: Context, - var calleeDependee: EOptionP[DefinedMethod, Callees] - ) - override protected def interpretArbitraryCall(target: PV, call: E)( implicit state: InterpretationState ): ProperPropertyComputationResult = { - val depender = CalleeDepender(target, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) + interpretArbitraryCallWithCallees(target) + } +} + +private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends L0FunctionCallInterpreter { + + implicit val ps: PropertyStore + implicit val contextProvider: ContextProvider + + override type CallState = CalleeDepender + + protected[this] case class CalleeDepender( + override val target: PV, + override val parameters: Seq[PV], + methodContext: Context, + var calleeDependee: EOptionP[DefinedMethod, Callees] + ) extends FunctionCallState(target, parameters) { + + override def hasDependees: Boolean = calleeDependee.isRefinable || super.hasDependees + + override def dependees: Iterable[SomeEOptionP] = super.dependees ++ Seq(calleeDependee).filter(_.isRefinable) + } + + protected def interpretArbitraryCallWithCallees(target: PV)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + val depender = CalleeDepender(target, params, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) - continuation(state, depender)(depender.calleeDependee.asInstanceOf[SomeEPS]) + if (depender.calleeDependee.isEPK) { + InterimResult.forUB( + InterpretationHandler.getEntity(state), + StringFlowFunctionProperty.ub, + Set(depender.calleeDependee), + continuation(state, depender) + ) + } else { + continuation(state, depender)(depender.calleeDependee.asInstanceOf[SomeEPS]) + } } - private def continuation( - state: InterpretationState, - depender: CalleeDepender + override protected[this] def continuation( + state: InterpretationState, + callState: CallState )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { - case FinalP(c: Callees) => - implicit val _state: InterpretationState = state - - val methods = getMethodsFromCallees(depender.methodContext, c) - if (methods.isEmpty) { - computeFinalLBFor(depender.target) + case UBP(c: Callees) => + callState.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] + val newMethods = getNewMethodsFromCallees(callState.methodContext, c)(state, callState) + if (newMethods.isEmpty && eps.isFinal) { + // Improve add previous results back + computeFinalLBFor(callState.target)(state) } else { - val tacDependees = methods.map(m => (m, ps(m, TACAI.key))).toMap - val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) - val callState = - FunctionCallState(state, depender.target, tacDependees.keys.toSeq, params, tacDependees) + for { + method <- newMethods + } { + callState.addCalledMethod(method, ps(method, TACAI.key)) + } - interpretArbitraryCallToFunctions(callState) + interpretArbitraryCallToFunctions(state, callState) } - case UBP(_: Callees) => - depender.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] - InterimResult.forUB( - InterpretationHandler.getEntity(state), - StringFlowFunctionProperty.ub, - Set(depender.calleeDependee), - continuation(state, depender) - ) - - case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") + case _ => super.continuation(state, callState)(eps) } } - private def getMethodsFromCallees(context: Context, callees: Callees)(implicit - state: InterpretationState + private def getNewMethodsFromCallees(context: Context, callees: Callees)(implicit + state: InterpretationState, + callState: CallState ): Seq[Method] = { - val methods = ListBuffer[Method]() // IMPROVE only process newest callees - callees.callees(context, state.pc).map(_.method).foreach { - case definedMethod: DefinedMethod => methods.append(definedMethod.definedMethod) - case _ => // IMPROVE add some uncertainty element if methods with unknown body exist - } - methods.sortBy(_.classFile.fqn).toList + callees.callees(context, state.pc) + // IMPROVE add some uncertainty element if methods with unknown body exist + .filter(_.method.hasSingleDefinedMethod) + .map(_.method.definedMethod) + .filterNot(callState.calleeMethods.contains) + .distinct.toList.sortBy(_.classFile.fqn) } } From 6811a898be0bfb19c8745f15a70cac3cdc66ace2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 16:49:58 +0200 Subject: [PATCH 475/583] Integrate system properties analysis --- .../string_analysis/l0/L0TestMethods.java | 11 ++ .../org/opalj/fpcf/StringAnalysisTest.scala | 12 +- .../br/fpcf/properties/SystemProperties.scala | 17 +- .../SystemPropertiesAnalysisScheduler.scala | 163 ---------------- .../L0StaticFunctionCallInterpreter.scala | 10 +- .../L0SystemPropertiesInterpreter.scala | 76 ++++++++ .../L1VirtualFunctionCallInterpreter.scala | 13 +- .../SystemPropertiesAnalysis.scala | 180 ++++++++++++++++++ .../SystemPropertiesState.scala | 46 +++++ 9 files changed, 353 insertions(+), 175 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/SystemPropertiesAnalysisScheduler.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 3839f745be..88b60b583c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1263,6 +1263,17 @@ public void fromStaticMethodWithParamTest() { analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); } + @StringDefinitionsCollection( + value = "tests that the string analysis neatly integrates with the system properties analysis", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "some.test.value") + }) + public void systemPropertiesIntegrationTest() { + System.setProperty("some.test.property", "some.test.value"); + String s = System.getProperty("some.test.property"); + analyzeString(s); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } 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 df7b47d061..c57f7335f3 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 @@ -28,6 +28,7 @@ import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalys import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.systemproperties.EagerSystemPropertiesAnalysisScheduler sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -209,7 +210,10 @@ sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { final def runL0Tests(): Unit = { describe("the org.opalj.fpcf.L0StringAnalysis is started") { - val as = executeAnalyses(LazyL0StringAnalysis.allRequiredAnalyses) + val as = executeAnalyses( + LazyL0StringAnalysis.allRequiredAnalyses :+ + EagerSystemPropertiesAnalysisScheduler + ) val entities = determineEntitiesToAnalyze(as.project) entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) @@ -255,7 +259,11 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { final def runL1Tests(): Unit = { describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses(LazyL1StringAnalysis.allRequiredAnalyses :+ EagerFieldAccessInformationAnalysis) + val as = executeAnalyses( + LazyL1StringAnalysis.allRequiredAnalyses :+ + EagerFieldAccessInformationAnalysis :+ + EagerSystemPropertiesAnalysisScheduler + ) val entities = determineEntitiesToAnalyze(as.project) // Currently broken L1 Tests diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala index d6a0db5bd9..7d68e15e99 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala @@ -4,6 +4,7 @@ package br package fpcf package properties +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason import org.opalj.fpcf.Property @@ -13,20 +14,26 @@ import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore /** - * TODO Documentation - * - * @author Florian Kuebler + * @author Maximilian Rüsch */ sealed trait SystemPropertiesPropertyMetaInformation extends PropertyMetaInformation { + type Self = SystemProperties } -class SystemProperties(val properties: Map[String, Set[String]]) +case class SystemProperties(values: Set[StringTreeNode]) extends Property with SystemPropertiesPropertyMetaInformation { + + def mergeWith(other: SystemProperties): SystemProperties = { + if (values == other.values) this + else SystemProperties(values ++ other.values) + } + final def key: PropertyKey[SystemProperties] = SystemProperties.key } object SystemProperties extends SystemPropertiesPropertyMetaInformation { + final val Name = "opalj.SystemProperties" final val key: PropertyKey[SystemProperties] = { @@ -35,7 +42,7 @@ object SystemProperties extends SystemPropertiesPropertyMetaInformation { (_: PropertyStore, reason: FallbackReason, _: Entity) => reason match { case PropertyIsNotDerivedByPreviouslyExecutedAnalysis => - new SystemProperties(Map.empty) + new SystemProperties(Set.empty) case _ => throw new IllegalStateException(s"analysis required for property: $Name") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/SystemPropertiesAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/SystemPropertiesAnalysisScheduler.scala deleted file mode 100644 index e35ccd3c9e..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/SystemPropertiesAnalysisScheduler.scala +++ /dev/null @@ -1,163 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses - -import org.opalj.br.Method -import org.opalj.br.ObjectType -import org.opalj.br.analyses.DeclaredMethodsKey -import org.opalj.br.analyses.ProjectInformationKeys -import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler -import org.opalj.br.fpcf.properties.SystemProperties -import org.opalj.br.fpcf.properties.cg.Callers -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.EPS -import org.opalj.fpcf.InterimEP -import org.opalj.fpcf.InterimEUBP -import org.opalj.fpcf.InterimPartialResult -import org.opalj.fpcf.PartialResult -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyBounds -import org.opalj.fpcf.PropertyKey -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Results -import org.opalj.fpcf.UBP -import org.opalj.tac.cg.TypeIteratorKey -import org.opalj.tac.fpcf.analyses.cg.ReachableMethodAnalysis -import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.value.ValueInformation - -class SystemPropertiesAnalysisScheduler private[analyses] ( - final val project: SomeProject -) extends ReachableMethodAnalysis { - - def processMethod( - callContext: ContextType, - tacaiEP: EPS[Method, TACAI] - ): ProperPropertyComputationResult = { - assert(tacaiEP.hasUBP && tacaiEP.ub.tac.isDefined) - val stmts = tacaiEP.ub.tac.get.stmts - - var propertyMap: Map[String, Set[String]] = Map.empty - - for (stmt <- stmts) stmt match { - case VirtualFunctionCallStatement(call) - if (call.name == "setProperty" || call.name == "put") && classHierarchy.isSubtypeOf( - call.declaringClass, - ObjectType("java/util/Properties") - ) => - propertyMap = computeProperties(propertyMap, call.params, stmts) - case StaticMethodCall(_, ObjectType.System, _, "setProperty", _, params) => - propertyMap = computeProperties(propertyMap, params, stmts) - case _ => - } - - if (propertyMap.isEmpty) { - return Results() - } - - def update( - currentVal: EOptionP[SomeProject, SystemProperties] - ): Option[InterimEP[SomeProject, SystemProperties]] = currentVal match { - case UBP(ub) => - var oldProperties = ub.properties - val noNewProperty = propertyMap.forall { - case (key, values) => - oldProperties.contains(key) && { - val oldValues = oldProperties(key) - values.forall(oldValues.contains) - } - } - - if (noNewProperty) { - None - } else { - for ((key, values) <- propertyMap) { - val oldValues = oldProperties.getOrElse(key, Set.empty) - oldProperties = oldProperties.updated(key, oldValues ++ values) - } - Some(InterimEUBP(project, new SystemProperties(propertyMap))) - } - - case _: EPK[SomeProject, SystemProperties] => - Some(InterimEUBP(project, new SystemProperties(propertyMap))) - } - - if (tacaiEP.isFinal) { - PartialResult[SomeProject, SystemProperties]( - project, - SystemProperties.key, - update - ) - } else { - InterimPartialResult( - project, - SystemProperties.key, - update, - Set(tacaiEP), - continuationForTAC(callContext.method) - ) - } - } - - def computeProperties( - propertyMap: Map[String, Set[String]], - params: Seq[Expr[DUVar[ValueInformation]]], - stmts: Array[Stmt[DUVar[ValueInformation]]] - ): Map[String, Set[String]] = { - var res = propertyMap - - assert(params.size == 2) - val possibleKeys = getPossibleStrings(params.head, stmts) - val possibleValues = getPossibleStrings(params(1), stmts) - - for (key <- possibleKeys) { - val values = res.getOrElse(key, Set.empty) - res = res.updated(key, values ++ possibleValues) - } - - res - } - - def getPossibleStrings( - value: Expr[DUVar[ValueInformation]], - stmts: Array[Stmt[DUVar[ValueInformation]]] - ): Set[String] = { - value.asVar.definedBy filter { index => index >= 0 && stmts(index).asAssignment.expr.isStringConst } map { - stmts(_).asAssignment.expr.asStringConst.value - } - } - -} - -object SystemPropertiesAnalysisScheduler extends BasicFPCFTriggeredAnalysisScheduler { - - override def requiredProjectInformation: ProjectInformationKeys = - Seq(DeclaredMethodsKey, TypeIteratorKey) - - override def uses: Set[PropertyBounds] = Set( - PropertyBounds.ub(Callers), - PropertyBounds.ub(TACAI) - ) - - override def triggeredBy: PropertyKey[Callers] = Callers.key - - override def register( - p: SomeProject, - ps: PropertyStore, - unused: Null - ): SystemPropertiesAnalysisScheduler = { - val analysis = new SystemPropertiesAnalysisScheduler(p) - ps.registerTriggeredComputation(triggeredBy, analysis.analyze) - analysis - } - - override def derivesEagerly: Set[PropertyBounds] = Set.empty - - override def derivesCollaboratively: Set[PropertyBounds] = Set( - PropertyBounds.ub(SystemProperties) - ) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 84a198107a..356144beab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -22,11 +22,13 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment */ case class L0StaticFunctionCallInterpreter()( implicit - override val p: SomeProject, - override val ps: PropertyStore + override val p: SomeProject, + override val ps: PropertyStore, + override val project: SomeProject ) extends AssignmentBasedStringInterpreter with L0ArbitraryStaticFunctionCallInterpreter - with L0StringValueOfFunctionCallInterpreter { + with L0StringValueOfFunctionCallInterpreter + with L0SystemPropertiesInterpreter { override type E = StaticFunctionCall[V] @@ -34,6 +36,8 @@ case class L0StaticFunctionCallInterpreter()( state: InterpretationState ): ProperPropertyComputationResult = { call.name match { + case "getProperty" if call.declaringClass == ObjectType.System => + interpretGetSystemPropertiesCall(target) case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) case _ if call.descriptor.returnType == ObjectType.String || diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala new file mode 100644 index 0000000000..1f85a6e18f --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala @@ -0,0 +1,76 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l0 +package interpretation + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.SystemProperties +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +private[string] trait L0SystemPropertiesInterpreter extends StringInterpreter { + + implicit val ps: PropertyStore + implicit val project: SomeProject + + private case class SystemPropertiesDepender(target: PV, var dependee: EOptionP[SomeProject, SystemProperties]) + + protected def interpretGetSystemPropertiesCall(target: PV)( + implicit state: InterpretationState + ): ProperPropertyComputationResult = { + val depender = SystemPropertiesDepender(target, ps(project, SystemProperties.key)) + + if (depender.dependee.isEPK) { + InterimResult.forUB( + InterpretationHandler.getEntity(state), + StringFlowFunctionProperty.constForVariableAt( + state.pc, + depender.target, + StringTreeNode.ub + ), + Set(depender.dependee), + continuation(state, depender) + ) + } + continuation(state, depender)(depender.dependee.asInstanceOf[SomeEPS]) + } + + private def continuation( + state: InterpretationState, + depender: SystemPropertiesDepender + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case UBP(ub: SystemProperties) => + depender.dependee = eps.asInstanceOf[EOptionP[SomeProject, SystemProperties]] + val newUB = StringFlowFunctionProperty.constForVariableAt( + state.pc, + depender.target, + StringTreeOr(ub.values.toSeq) + ) + if (depender.dependee.isRefinable) { + InterimResult.forUB( + InterpretationHandler.getEntity(state), + newUB, + Set(depender.dependee), + continuation(state, depender) + ) + } else { + computeFinalResult(newUB)(state) + } + + case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 2ba91c9acc..d05887a517 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -9,6 +9,8 @@ package interpretation import org.opalj.br.DefinedMethod import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callees @@ -21,6 +23,7 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0SystemPropertiesInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -33,9 +36,11 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class L1VirtualFunctionCallInterpreter( implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider + implicit val contextProvider: ContextProvider, + implicit val project: SomeProject ) extends L0VirtualFunctionCallInterpreter with StringInterpreter + with L0SystemPropertiesInterpreter with L1ArbitraryVirtualFunctionCallInterpreter { override type E = VirtualFunctionCall[V] @@ -43,7 +48,11 @@ class L1VirtualFunctionCallInterpreter( override protected def interpretArbitraryCall(target: PV, call: E)( implicit state: InterpretationState ): ProperPropertyComputationResult = { - interpretArbitraryCallWithCallees(target) + if (call.name == "getProperty" && call.declaringClass == ObjectType("java/util/Properties")) { + interpretGetSystemPropertiesCall(target) + } else { + interpretArbitraryCallWithCallees(target) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala new file mode 100644 index 0000000000..24df97e801 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala @@ -0,0 +1,180 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package systemproperties + +import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler +import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler +import org.opalj.br.fpcf.properties.SystemProperties +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK +import org.opalj.fpcf.EPS +import org.opalj.fpcf.EUBP +import org.opalj.fpcf.InterimEP +import org.opalj.fpcf.InterimEUBP +import org.opalj.fpcf.InterimPartialResult +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyKind +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.cg.TypeIteratorKey +import org.opalj.tac.fpcf.analyses.cg.ReachableMethodAnalysis +import org.opalj.tac.fpcf.analyses.string.VariableContext +import org.opalj.tac.fpcf.properties.TACAI + +/** + * @author Maximilian Rüsch + */ +class SystemPropertiesAnalysis private[analyses] ( + final val project: SomeProject +) extends ReachableMethodAnalysis { + + type State = SystemPropertiesState[ContextType] + private type Values = Set[StringTreeNode] + + def processMethod(callContext: ContextType, tacaiEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { + implicit val state: State = new SystemPropertiesState(callContext, tacaiEP, Map.empty) + + var values: Values = Set.empty + for (stmt <- state.tac.stmts) stmt match { + case VirtualFunctionCallStatement(call) + if (call.name == "setProperty" || call.name == "put") && + classHierarchy.isSubtypeOf(call.declaringClass, ObjectType("java/util/Properties")) => + values ++= getPossibleStrings(call.pc, call.params(1)) + + case StaticFunctionCallStatement(call) + if call.name == "setProperty" && call.declaringClass == ObjectType.System => + values ++= getPossibleStrings(call.pc, call.params(1)) + + case StaticMethodCall(pc, ObjectType.System, _, "setProperty", _, params) => + values ++= getPossibleStrings(pc, params(1)) + + case _ => + } + + returnResults(values) + } + + def returnResults(values: Set[StringTreeNode])(implicit state: State): ProperPropertyComputationResult = { + def update(currentVal: EOptionP[SomeProject, SystemProperties]): Option[InterimEP[SomeProject, SystemProperties]] = { + currentVal match { + case UBP(ub) => + val newUB = ub.mergeWith(SystemProperties(values)) + if (newUB eq ub) None + else Some(InterimEUBP(project, newUB)) + + case _: EPK[SomeProject, SystemProperties] => + Some(InterimEUBP(project, SystemProperties(values))) + } + } + + if (state.hasOpenDependencies) { + InterimPartialResult( + project, + SystemProperties.key, + update, + state.dependees, + continuation(state) + ) + } else { + PartialResult(project, SystemProperties.key, update) + } + } + + def continuation(state: State)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case _ if eps.pk == TACAI.key => + continuationForTAC(state.callContext.method)(eps) + + case eps @ EUBP(_: VariableContext, ub: StringConstancyProperty) => + state.updateStringDependee(eps.asInstanceOf[EPS[VariableContext, StringConstancyProperty]]) + returnResults(Set(ub.sci.tree))(state) + + case _ => + throw new IllegalArgumentException(s"unexpected eps $eps") + } + } + + def getPossibleStrings(pc: Int, value: Expr[V])(implicit state: State): Set[StringTreeNode] = { + ps( + VariableContext(pc, value.asVar.toPersistentForm(state.tac.stmts), state.callContext), + StringConstancyProperty.key + ) match { + case eps @ UBP(ub) => + state.updateStringDependee(eps) + Set(ub.sci.tree) + + case epk: EOptionP[VariableContext, StringConstancyProperty] => + state.updateStringDependee(epk) + Set.empty + } + } +} + +object TriggeredSystemPropertiesAnalysisScheduler extends BasicFPCFTriggeredAnalysisScheduler { + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeIteratorKey) + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs( + Callers, + TACAI, + StringConstancyProperty, + SystemProperties + ) + + override def triggeredBy: PropertyKind = Callers + + override def register( + p: SomeProject, + ps: PropertyStore, + unused: Null + ): SystemPropertiesAnalysis = { + val analysis = new SystemPropertiesAnalysis(p) + ps.registerTriggeredComputation(Callers.key, analysis.analyze) + analysis + } + + override def derivesEagerly: Set[PropertyBounds] = Set.empty + + override def derivesCollaboratively: Set[PropertyBounds] = PropertyBounds.ubs(SystemProperties) +} + +object EagerSystemPropertiesAnalysisScheduler extends BasicFPCFEagerAnalysisScheduler { + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeIteratorKey) + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs( + Callers, + TACAI, + StringConstancyProperty, + SystemProperties + ) + + override def start( + p: SomeProject, + ps: PropertyStore, + unused: Null + ): SystemPropertiesAnalysis = { + val analysis = new SystemPropertiesAnalysis(p) + val dm = p.get(DeclaredMethodsKey) + ps.scheduleEagerComputationsForEntities(p.allMethods.map(dm.apply))(analysis.analyze) + analysis + } + + override def derivesEagerly: Set[PropertyBounds] = Set.empty + + override def derivesCollaboratively: Set[PropertyBounds] = PropertyBounds.ubs(SystemProperties) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala new file mode 100644 index 0000000000..b442ff16cc --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala @@ -0,0 +1,46 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package systemproperties + +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.SomeEOptionP +import org.opalj.tac.fpcf.analyses.cg.BaseAnalysisState +import org.opalj.tac.fpcf.analyses.string.VariableContext +import org.opalj.tac.fpcf.properties.TACAI + +final class SystemPropertiesState[ContextType <: Context]( + override val callContext: ContextType, + override protected[this] var _tacDependee: EOptionP[Method, TACAI], + private[this] var _stringConstancyDependees: Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] +) extends BaseAnalysisState with TACAIBasedAnalysisState[ContextType] { + + ///////////////////////////////////////////// + // // + // strings // + // // + ///////////////////////////////////////////// + + def updateStringDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = { + _stringConstancyDependees = _stringConstancyDependees.updated(dependee.e, dependee) + } + + ///////////////////////////////////////////// + // // + // general dependency management // + // // + ///////////////////////////////////////////// + + override def hasOpenDependencies: Boolean = { + super.hasOpenDependencies || + _stringConstancyDependees.valuesIterator.exists(_.isRefinable) + } + + override def dependees: Set[SomeEOptionP] = + super.dependees ++ _stringConstancyDependees.valuesIterator.filter(_.isRefinable) +} From e2c140380f6659a2dab62215cbd266ddf406f74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 18:32:49 +0200 Subject: [PATCH 476/583] Fix function call interpreter for non-defined method dependees --- .../l0/interpretation/L0FunctionCallInterpreter.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala index a0416336c9..98b4f80a8e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala @@ -81,10 +81,10 @@ trait L0FunctionCallInterpreter ): ProperPropertyComputationResult = { callState.calleeMethods.foreach { m => val tacEOptP = callState.tacDependees(m) - if (tacEOptP.isFinal) { - val calleeTac = tacEOptP.asFinal.p.tac + if (tacEOptP.hasUBP) { + val calleeTac = tacEOptP.ub.tac if (calleeTac.isEmpty) { - // When the tac ep is final but we still do not have a callee tac, we cannot infer arbitrary call values at all + // When we do not have a callee tac, we cannot infer arbitrary call return values at all callState.hasUnresolvableReturnValue += m -> true } else { val returns = calleeTac.get.stmts.toIndexedSeq.filter(stmt => stmt.isInstanceOf[ReturnValue[V]]) @@ -119,12 +119,14 @@ trait L0FunctionCallInterpreter callState.calleeMethods.map { m => if (callState.hasUnresolvableReturnValue(m)) { StringTreeNode.lb - } else { + } else if (callState.returnDependees.contains(m)) { StringTreeOr(callState.returnDependees(m).map { rd => if (rd.hasUBP) { rd.ub.sci.tree.replaceParameters(parameters.map { kv => (kv._1, env(pc, kv._2)) }) } else StringTreeNode.ub }) + } else { + StringTreeNode.ub } } } From 8a1ed5e16b077bcc4eef6960f45aef712861ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 29 Jun 2024 18:33:11 +0200 Subject: [PATCH 477/583] Improve structural analysis performance --- .../flowanalysis/StructuralAnalysis.scala | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index e7d37e45cd..d58687d852 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -44,8 +44,7 @@ object StructuralAnalysis { var newSuperGraph: SuperFlowGraph = currentSuperGraph // Compact - newGraph = newGraph.incl(newRegion) - newSuperGraph = newSuperGraph.incl(newRegion) + // Note that adding the new region to the graph and superGraph is done anyways since we add edges later val maxPost = post.indexOf(subNodes.maxBy(post.indexOf)) post(maxPost) = newRegion // Removing old regions from the graph is done later @@ -53,24 +52,21 @@ object StructuralAnalysis { postCtr = post.indexOf(newRegion) // Replace edges - for { - e <- newGraph.edges - } { - val source: FlowGraphNode = e.outer.source - val target: FlowGraphNode = e.outer.target - - if (!subNodes.contains(source) && subNodes.contains(target)) { - newGraph += DiEdge(source, newRegion) - newSuperGraph += DiEdge(source, newRegion) - newSuperGraph -= DiEdge(source, target) - } else if (subNodes.contains(source) && !subNodes.contains(target)) { - newGraph += DiEdge(newRegion, target) - newSuperGraph += DiEdge(newRegion, target) - newSuperGraph -= DiEdge(source, target) - } + val incomingEdges = currentGraph.edges.filter { e => + !subNodes.contains(e.outer.source) && subNodes.contains(e.outer.target) + } + val outgoingEdges = currentGraph.edges.filter { e => + subNodes.contains(e.outer.source) && !subNodes.contains(e.outer.target) } + + newGraph ++= incomingEdges.map(e => DiEdge(e.outer.source, newRegion)) + .concat(outgoingEdges.map(e => DiEdge(newRegion, e.outer.target))) newGraph = newGraph.removedAll(subNodes, Set.empty) - newSuperGraph = newSuperGraph.incl(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get)) + + newSuperGraph ++= incomingEdges.map(e => DiEdge(e.outer.source, newRegion)) + .concat(outgoingEdges.map(e => DiEdge(newRegion, e.outer.target))) + newSuperGraph --= incomingEdges.concat(outgoingEdges).map(e => DiEdge(e.outer.source, e.outer.target)) + .concat(Seq(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get))) (newGraph, newSuperGraph, newRegion) } @@ -80,8 +76,8 @@ object StructuralAnalysis { while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) - val gPost = post.map(g.get).reverse.asInstanceOf[mutable.ListBuffer[FlowGraph#NodeT]] - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPost, n) + val gPostMap = post.reverse.zipWithIndex.map(ni => (g.get(ni._1), ni._2)).toMap + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -89,11 +85,7 @@ object StructuralAnalysis { val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, arType) g = newGraph sg = newSuperGraph - for { - node <- nodes - } { - controlTree = controlTree.incl(DiEdge(newRegion, node)) - } + controlTree = controlTree.concat(nodes.map(node => DiEdge(newRegion, node))) if (nodes.contains(curEntry)) { curEntry = newRegion @@ -128,11 +120,7 @@ object StructuralAnalysis { val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, crType) g = newGraph sg = newSuperGraph - for { - node <- nodes - } { - controlTree = controlTree.incl(DiEdge(newRegion, node)) - } + controlTree = controlTree.concat(nodes.map(node => DiEdge(newRegion, node))) if (nodes.contains(curEntry)) { curEntry = newRegion @@ -156,19 +144,21 @@ object StructuralAnalysis { if (m == n) { false } else { - val nonNFromMTraverser = graph.innerNodeTraverser(graph.get(m), subgraphNodes = _.outer != n) - graph.nodes.outerIterable.exists { k => - k != n && - graph.find(DiEdge(k, n)).isDefined && - nonNFromMTraverser.pathTo(graph.get(k)).isDefined && - domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(k)) + val innerN = graph.get(n) + val nonNFromMTraverser = graph.innerNodeTraverser(graph.get(m), subgraphNodes = _ != innerN) + val predecessorsOfN = innerN.diPredecessors + graph.nodes.exists { innerK => + innerK != innerN && + predecessorsOfN.contains(innerK) && + domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(innerK.outer)) && + nonNFromMTraverser.pathTo(innerK).isDefined } } } private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( graph: G, - postOrderTraversal: mutable.ListBuffer[G#NodeT], + postOrderTraversal: Map[G#NodeT, Int], startingNode: A ): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] @@ -202,9 +192,9 @@ object StructuralAnalysis { def isStillAcyclic: Boolean = { currentSuccessors.forall { node => - val postOrderIndex = postOrderTraversal.indexOf(node) + val postOrderIndex = postOrderTraversal(node) - node.diSuccessors.forall(successor => postOrderTraversal.indexOf(successor) >= postOrderIndex) + node.diSuccessors.forall(successor => postOrderTraversal(successor) >= postOrderIndex) } } @@ -302,6 +292,8 @@ object StructuralAnalysis { if (enteringNodes.size > 1) { throw new IllegalStateException("Found more than one entering node for a natural loop!") + } else if (enteringNodes.isEmpty) { + throw new IllegalStateException("Found more no entering node for a natural loop!") } Some((NaturalLoop, reachUnder, enteringNodes.head)) From 07312ba12d6599fda496d009345386cdc2793801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 30 Jun 2024 13:37:28 +0200 Subject: [PATCH 478/583] Add global entry to flow graphs --- .../tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala | 2 +- .../fpcf/analyses/string/flowanalysis/FlowGraphNode.scala | 6 ++++++ .../analyses/string/flowanalysis/StructuralAnalysis.scala | 2 +- .../tac/fpcf/analyses/string/flowanalysis/package.scala | 6 ++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 7c26d7ac86..43dba67ef9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -66,7 +66,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn state.flowGraph = FlowGraph(tac.cfg) val (_, superFlowGraph, controlTree) = - StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entryFromCFG(tac.cfg)) + StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entry) state.superFlowGraph = superFlowGraph state.controlTree = controlTree diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index c71a417e9f..0a86b36b0b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -39,6 +39,12 @@ case class Statement(nodeId: Int) extends FlowGraphNode { override def toString: String = s"Statement($nodeId)" } +object GlobalEntry extends FlowGraphNode { + override val nodeIds: Set[Int] = Set(Int.MinValue + 1) + + override def toString: String = s"GlobalEntry" +} + object GlobalExit extends FlowGraphNode { override val nodeIds: Set[Int] = Set(Int.MinValue) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index d58687d852..5c7e7da691 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -293,7 +293,7 @@ object StructuralAnalysis { if (enteringNodes.size > 1) { throw new IllegalStateException("Found more than one entering node for a natural loop!") } else if (enteringNodes.isEmpty) { - throw new IllegalStateException("Found more no entering node for a natural loop!") + throw new IllegalStateException("Found no entering node for a natural loop!") } Some((NaturalLoop, reachUnder, enteringNodes.head)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index c158ecd2b7..30f1a828d5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -36,6 +36,8 @@ package object flowanalysis { object FlowGraph extends TypedGraphFactory[FlowGraphNode, DiEdge[FlowGraphNode]] { + def entry: FlowGraphNode = GlobalEntry + private def mapInstrIndexToPC[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]])(index: Int): Int = { if (index >= 0) cfg.code.instructions(index).pc else index @@ -63,7 +65,7 @@ package object flowanalysis { case n => n.successors.map(s => DiEdge(Statement(toPC(n.nodeId)), Statement(toPC(s.nodeId)))) }.toSet - val g = Graph.from(edges) + val g = Graph.from(edges + DiEdge(entry, entryFromCFG(cfg))) val normalReturnNode = Statement(cfg.normalReturnNode.nodeId) val abnormalReturnNode = Statement(cfg.abnormalReturnNode.nodeId) @@ -87,7 +89,7 @@ package object flowanalysis { } } - def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = + private[this] def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = Statement(mapInstrIndexToPC(cfg)(cfg.startBlock.nodeId)) def toDot[N <: FlowGraphNode, E <: Edge[N]](graph: Graph[N, E]): String = { From 739036a3641100b5ce481b703c8810babf90c247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 30 Jun 2024 15:21:05 +0200 Subject: [PATCH 479/583] Start optimizing memory footprint of data flow analysis --- .../flowanalysis/DataFlowAnalysis.scala | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 77873dd079..c35cf9060b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -39,21 +39,23 @@ object DataFlowAnalysis { env: StringTreeEnvironment ): StringTreeEnvironment = { val pipe = pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc) _ - val childNodes = controlTree.get(node).diSuccessors.map(_.outer) - val limitedFlowGraph = superFlowGraph.filter(n => childNodes.contains(n.outer)) + val innerChildNodes = controlTree.get(node).diSuccessors.map(n => superFlowGraph.get(n.outer)) def processBlock(entry: FlowGraphNode): StringTreeEnvironment = { var currentEnv = env - var currentNode = limitedFlowGraph.get(entry) - while (currentNode.diSuccessors.nonEmpty) { + for { + currentNode <- superFlowGraph.innerNodeTraverser( + superFlowGraph.get(entry), + subgraphNodes = innerChildNodes.contains + ) + } { currentEnv = pipe(currentNode.outer, currentEnv) - currentNode = currentNode.diSuccessors.head } - - pipe(currentNode, currentEnv) + currentEnv } def processIfThenElse(entry: FlowGraphNode): StringTreeEnvironment = { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) val entryNode = limitedFlowGraph.get(entry) val successors = entryNode.diSuccessors.map(_.outer).toList.sorted val branches = (successors.head, successors.tail.head) @@ -65,6 +67,7 @@ object DataFlowAnalysis { } def processIfThen(entry: FlowGraphNode): StringTreeEnvironment = { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) val entryNode = limitedFlowGraph.get(entry) val (yesBranch, noBranch) = if (entryNode.diSuccessors.head.diSuccessors.nonEmpty) { (entryNode.diSuccessors.head, entryNode.diSuccessors.tail.head) @@ -82,6 +85,7 @@ object DataFlowAnalysis { } def processProper(entry: FlowGraphNode): StringTreeEnvironment = { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) val entryNode = limitedFlowGraph.get(entry) var sortedCurrentNodes = List(entryNode) @@ -115,6 +119,7 @@ object DataFlowAnalysis { } def processWhileLoop(entry: FlowGraphNode): StringTreeEnvironment = { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) val entryNode = limitedFlowGraph.get(entry) val envAfterEntry = pipe(entry, env) @@ -131,6 +136,7 @@ object DataFlowAnalysis { } def processNaturalLoop(entry: FlowGraphNode): StringTreeEnvironment = { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors val removedBackEdgesGraph = limitedFlowGraph.filterNot( edgeP = edge => From 8dfb5d4a7fda377de7aff22bc0394590023cb318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 30 Jun 2024 15:49:12 +0200 Subject: [PATCH 480/583] Slightly optimize index building for dominator tree during structural analysis --- .../flowanalysis/StructuralAnalysis.scala | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 5c7e7da691..7e48ac2f48 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -91,16 +91,13 @@ object StructuralAnalysis { curEntry = newRegion } } else { - val indexedNodes = g.nodes.outerIterable.toList + val indexedNodes = g.nodes.toIndexedSeq + val indexOf = indexedNodes.zipWithIndex.toMap val domTree = DominatorTree( - indexedNodes.indexOf(curEntry), + indexOf(g.get(curEntry)), g.get(curEntry).diPredecessors.nonEmpty, - index => { f => - g.get(indexedNodes(index)).diSuccessors.foreach(ds => f(indexedNodes.indexOf(ds.outer))) - }, - index => { f => - g.get(indexedNodes(index)).diPredecessors.foreach(ds => f(indexedNodes.indexOf(ds.outer))) - }, + index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, + index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, indexedNodes.size - 1 ) @@ -108,7 +105,7 @@ object StructuralAnalysis { for { m <- g.nodes.outerIterator if !controlTree.contains(m) || controlTree.get(m).diPredecessors.isEmpty - if StructuralAnalysis.pathBack(g, indexedNodes, domTree)(m, n) + if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, indexOf, domTree)(m, n) } { reachUnder = reachUnder.incl(m) } @@ -137,7 +134,7 @@ object StructuralAnalysis { (g, sg, controlTree) } - private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexedNodes: Seq[A], domTree: DominatorTree)( + private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexOf: Map[G#NodeT, Int], domTree: DominatorTree)( m: A, n: A ): Boolean = { @@ -150,7 +147,7 @@ object StructuralAnalysis { graph.nodes.exists { innerK => innerK != innerN && predecessorsOfN.contains(innerK) && - domTree.strictlyDominates(indexedNodes.indexOf(n), indexedNodes.indexOf(innerK.outer)) && + domTree.strictlyDominates(indexOf(innerN), indexOf(innerK)) && nonNFromMTraverser.pathTo(innerK).isDefined } } From dd4a0e08a914c81f4e96f5823280becfc1d0be87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 30 Jun 2024 16:44:08 +0200 Subject: [PATCH 481/583] Optimize some predecessor checks --- .../analyses/string/flowanalysis/StructuralAnalysis.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 7e48ac2f48..b689fd9ded 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -95,7 +95,7 @@ object StructuralAnalysis { val indexOf = indexedNodes.zipWithIndex.toMap val domTree = DominatorTree( indexOf(g.get(curEntry)), - g.get(curEntry).diPredecessors.nonEmpty, + g.get(curEntry).hasPredecessors, index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, indexedNodes.size - 1 @@ -104,7 +104,8 @@ object StructuralAnalysis { var reachUnder = Set(n) for { m <- g.nodes.outerIterator - if !controlTree.contains(m) || controlTree.get(m).diPredecessors.isEmpty + innerM = controlTree.find(m) + if innerM.isEmpty || !innerM.get.hasPredecessors if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, indexOf, domTree)(m, n) } { reachUnder = reachUnder.incl(m) From 99adecf810f3139c553f3ecf88dbc452f3a41777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 1 Jul 2024 23:21:26 +0200 Subject: [PATCH 482/583] Fix acyclic regions and greatly improve acyclic region performance --- .../flowanalysis/StructuralAnalysis.scala | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index b689fd9ded..2bdb7082c9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -20,14 +20,16 @@ import scalax.collection.immutable.Graph */ object StructuralAnalysis { + private final val maxIterations = 1000 + def analyze(graph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { var g = graph var sg = graph.asInstanceOf[SuperFlowGraph] var curEntry = entry var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] - var outerIterations = 0 - while (g.order > 1 && outerIterations < 10000) { + var iterations = 0 + while (g.order > 1 && iterations < maxIterations) { // Find post order depth first traversal order for nodes var postCtr = 1 val post = mutable.ListBuffer.empty[FlowGraphNode] @@ -76,8 +78,17 @@ object StructuralAnalysis { while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) + val indexedNodes = g.nodes.toIndexedSeq + val indexOf = indexedNodes.zipWithIndex.toMap + val domTree = DominatorTree( + indexOf(g.get(curEntry)), + g.get(curEntry).hasPredecessors, + index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, + index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, + indexedNodes.size - 1 + ) val gPostMap = post.reverse.zipWithIndex.map(ni => (g.get(ni._1), ni._2)).toMap - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, n) + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, n, indexedNodes, domTree) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -91,16 +102,6 @@ object StructuralAnalysis { curEntry = newRegion } } else { - val indexedNodes = g.nodes.toIndexedSeq - val indexOf = indexedNodes.zipWithIndex.toMap - val domTree = DominatorTree( - indexOf(g.get(curEntry)), - g.get(curEntry).hasPredecessors, - index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, - index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, - indexedNodes.size - 1 - ) - var reachUnder = Set(n) for { m <- g.nodes.outerIterator @@ -129,7 +130,11 @@ object StructuralAnalysis { } } - outerIterations += 1 + iterations += 1 + } + + if (iterations >= maxIterations) { + throw new IllegalStateException(s"Could not reduce tree in $maxIterations iterations!") } (g, sg, controlTree) @@ -157,7 +162,9 @@ object StructuralAnalysis { private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( graph: G, postOrderTraversal: Map[G#NodeT, Int], - startingNode: A + startingNode: A, + indexedNodes: IndexedSeq[G#NodeT], + domTree: DominatorTree ): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] var entry: graph.NodeT = graph.get(startingNode) @@ -184,32 +191,37 @@ object StructuralAnalysis { entry = n } - def locateProperAcyclicInterval: Option[AcyclicRegionType] = { - var currentNodeSet = Set(n) - var currentSuccessors = n.diSuccessors - - def isStillAcyclic: Boolean = { - currentSuccessors.forall { node => - val postOrderIndex = postOrderTraversal(node) - - node.diSuccessors.forall(successor => postOrderTraversal(successor) >= postOrderIndex) + def isAcyclic(nodes: Set[graph.NodeT]): Boolean = { + nodes.forall { node => + val postOrderIndex = postOrderTraversal(node) + node.diSuccessors.forall { successor => + !nodes.contains(successor) || postOrderTraversal(successor) >= postOrderIndex } } + } - var stillAcyclic = isStillAcyclic - while (currentSuccessors.size > 1 && stillAcyclic) { - currentNodeSet = currentNodeSet ++ currentSuccessors - currentSuccessors = currentSuccessors.flatMap(node => node.diSuccessors) - stillAcyclic = isStillAcyclic + def locateProperAcyclicInterval: Option[AcyclicRegionType] = { + val zippedImmediateDominators = domTree.immediateDominators.zipWithIndex; + var accumulatedDominatedIndexes = Set.empty[Int]; + var newDominatedIndexes = Set(indexedNodes.indexOf(n)) + while (newDominatedIndexes.nonEmpty) { + accumulatedDominatedIndexes = accumulatedDominatedIndexes.union(newDominatedIndexes) + newDominatedIndexes = newDominatedIndexes + .flatMap(ndi => zippedImmediateDominators.filter(ndi == _._1).map(_._2)) + .diff(accumulatedDominatedIndexes) } - val allPredecessors = currentNodeSet.excl(n).flatMap(node => node.diPredecessors) - if (!stillAcyclic) { - None - } else if (!allPredecessors.equals(currentNodeSet.diff(currentSuccessors))) { + val dominatedNodes = accumulatedDominatedIndexes.map(indexedNodes).asInstanceOf[Set[graph.NodeT]] + if (dominatedNodes.size == 1 || + !isAcyclic(dominatedNodes) || + // Check if no dominated node is reached from an non-dominated node + !dominatedNodes.excl(n).forall(_.diPredecessors.subsetOf(dominatedNodes)) || + // Check if all dominated nodes agree on a single successor outside the set (if it exists) + dominatedNodes.flatMap(node => node.diSuccessors.diff(dominatedNodes)).size > 1 + ) { None } else { - nSet = currentNodeSet ++ currentSuccessors + nSet = dominatedNodes entry = n Some(Proper) @@ -220,7 +232,7 @@ object StructuralAnalysis { val rType = if (nSet.size > 1) { // Condition is added to ensure chosen bb does not contain any self loops or other cyclic stuff // IMPROVE weaken to allow back edges from the "last" nSet member to the first to enable reductions to self loops - if (graph.filter(nSet.contains(_)).isAcyclic) + if (isAcyclic(nSet)) Some(Block) else None From 9815bb04b1d04f35e174b1648402ae8a56e40c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 3 Jul 2024 22:12:19 +0200 Subject: [PATCH 483/583] Speed up post order traversal by using inner elements --- .../flowanalysis/StructuralAnalysis.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 2bdb7082c9..3192bcfb1e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -317,24 +317,23 @@ object PostOrderTraversal { def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( implicit ordering: Ordering[A] ): Unit = { - var visited = Set.empty[A] + implicit val innerOrdering: Ordering[graph.NodeT] = ordering.on(_.outer) + var visited = Set.empty[graph.NodeT] def foreachInTraversal( - graph: G, - node: A - )(nodeHandler: A => Unit)(implicit ordering: Ordering[A]): Unit = { + node: graph.NodeT + )(nodeHandler: A => Unit): Unit = { visited = visited + node for { - successor <- (graph.get(node).diSuccessors.map(_.outer) -- visited).toList.sorted - if !visited.contains(successor) + successor <- (node.diSuccessors -- visited).toList.sorted } { - foreachInTraversal(graph, successor)(nodeHandler) + foreachInTraversal(successor)(nodeHandler) } - nodeHandler(node) + nodeHandler(node.outer) } - foreachInTraversal(graph, initial)(nodeHandler) + foreachInTraversal(graph.get(initial))(nodeHandler) } } From 9edd5e9e9e21b523cf43138696aa17526882407d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 4 Jul 2024 23:10:01 +0200 Subject: [PATCH 484/583] Implement mutable dominator information --- .../flowanalysis/StructuralAnalysis.scala | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 3192bcfb1e..b8164106b9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -28,6 +28,9 @@ object StructuralAnalysis { var curEntry = entry var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] + var (indexedNodes, indexOf, immediateDominators, allDominators) = computeDominators(g, entry) + def strictlyDominates(n: FlowGraphNode, w: FlowGraphNode): Boolean = n != w && allDominators(w).contains(n) + var iterations = 0 while (g.order > 1 && iterations < maxIterations) { // Find post order depth first traversal order for nodes @@ -70,6 +73,25 @@ object StructuralAnalysis { newSuperGraph --= incomingEdges.concat(outgoingEdges).map(e => DiEdge(e.outer.source, e.outer.target)) .concat(Seq(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get))) + // Update dominator data + indexedNodes = indexedNodes.filterNot(subNodes.contains).appended(newRegion) + indexOf = indexedNodes.zipWithIndex.toMap + + val commonDominators = subNodes.map(allDominators).reduce(_.intersect(_)) + allDominators.subtractAll(subNodes).update(newRegion, commonDominators) + allDominators = allDominators.map(kv => + ( + kv._1, { + val intersection = kv._2.intersect(subNodes.toSeq) + if (intersection.nonEmpty) { + val index = kv._2.indexWhere(intersection.contains) + kv._2.patch(index, Seq(newRegion), intersection.size) + } else kv._2 + } + ) + ) + immediateDominators = allDominators.map(kv => (kv._1, kv._2.head)) + (newGraph, newSuperGraph, newRegion) } @@ -78,17 +100,14 @@ object StructuralAnalysis { while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) - val indexedNodes = g.nodes.toIndexedSeq - val indexOf = indexedNodes.zipWithIndex.toMap - val domTree = DominatorTree( - indexOf(g.get(curEntry)), - g.get(curEntry).hasPredecessors, - index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, - index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, - indexedNodes.size - 1 - ) val gPostMap = post.reverse.zipWithIndex.map(ni => (g.get(ni._1), ni._2)).toMap - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, n, indexedNodes, domTree) + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion( + g, + gPostMap, + indexedNodes, + indexOf, + immediateDominators.map(kv => (indexOf(kv._1), indexOf(kv._2))).toMap + )(n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -107,7 +126,7 @@ object StructuralAnalysis { m <- g.nodes.outerIterator innerM = controlTree.find(m) if innerM.isEmpty || !innerM.get.hasPredecessors - if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, indexOf, domTree)(m, n) + if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, strictlyDominates)(m, n) } { reachUnder = reachUnder.incl(m) } @@ -140,7 +159,43 @@ object StructuralAnalysis { (g, sg, controlTree) } - private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, indexOf: Map[G#NodeT, Int], domTree: DominatorTree)( + private def computeDominators[A, G <: Graph[A, DiEdge[A]]]( + graph: G, + entry: A + ): (IndexedSeq[A], Map[A, Int], mutable.Map[A, A], mutable.Map[A, Seq[A]]) = { + val indexedNodes = graph.nodes.toIndexedSeq + val indexOf = indexedNodes.zipWithIndex.toMap + val domTree = DominatorTree( + indexOf(graph.get(entry)), + graph.get(entry).hasPredecessors, + index => { f => indexedNodes(index).diSuccessors.foreach(ds => f(indexOf(ds))) }, + index => { f => indexedNodes(index).diPredecessors.foreach(ds => f(indexOf(ds))) }, + indexedNodes.size - 1 + ) + val outerIndexedNodes = indexedNodes.map(_.outer) + val immediateDominators = mutable.Map.from { + domTree.immediateDominators.zipWithIndex.map(iDomWithIndex => { + (outerIndexedNodes(iDomWithIndex._2), outerIndexedNodes(iDomWithIndex._1)) + }) + } + immediateDominators.update(entry, entry) + + def getAllDominators(n: A): Seq[A] = { + val builder = Seq.newBuilder[A] + var c = n + while (c != entry) { + builder.addOne(c) + c = immediateDominators(c) + } + builder.addOne(entry) + builder.result() + } + val allDominators = immediateDominators.map(kv => (kv._1, getAllDominators(kv._2))) + + (outerIndexedNodes, indexOf.map(kv => (kv._1.outer, kv._2)), immediateDominators, allDominators) + } + + private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, strictlyDominates: (A, A) => Boolean)( m: A, n: A ): Boolean = { @@ -153,19 +208,19 @@ object StructuralAnalysis { graph.nodes.exists { innerK => innerK != innerN && predecessorsOfN.contains(innerK) && - domTree.strictlyDominates(indexOf(innerN), indexOf(innerK)) && + strictlyDominates(n, innerK.outer) && nonNFromMTraverser.pathTo(innerK).isDefined } } } private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( - graph: G, - postOrderTraversal: Map[G#NodeT, Int], - startingNode: A, - indexedNodes: IndexedSeq[G#NodeT], - domTree: DominatorTree - ): (A, Option[(AcyclicRegionType, Set[A], A)]) = { + graph: G, + postOrderTraversal: Map[G#NodeT, Int], + indexedNodes: IndexedSeq[A], + indexOf: Map[A, Int], + immediateDominators: Map[Int, Int] + )(startingNode: A): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] var entry: graph.NodeT = graph.get(startingNode) @@ -201,9 +256,9 @@ object StructuralAnalysis { } def locateProperAcyclicInterval: Option[AcyclicRegionType] = { - val zippedImmediateDominators = domTree.immediateDominators.zipWithIndex; + val zippedImmediateDominators = immediateDominators.map[(Int, Int)](kv => (kv._2, kv._1)); var accumulatedDominatedIndexes = Set.empty[Int]; - var newDominatedIndexes = Set(indexedNodes.indexOf(n)) + var newDominatedIndexes = Set(indexOf(n)) while (newDominatedIndexes.nonEmpty) { accumulatedDominatedIndexes = accumulatedDominatedIndexes.union(newDominatedIndexes) newDominatedIndexes = newDominatedIndexes @@ -211,7 +266,7 @@ object StructuralAnalysis { .diff(accumulatedDominatedIndexes) } - val dominatedNodes = accumulatedDominatedIndexes.map(indexedNodes).asInstanceOf[Set[graph.NodeT]] + val dominatedNodes = accumulatedDominatedIndexes.map(di => graph.get(indexedNodes(di))) if (dominatedNodes.size == 1 || !isAcyclic(dominatedNodes) || // Check if no dominated node is reached from an non-dominated node From 961f5e462ae6437053aeca4fab4e183e06ab3583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 4 Jul 2024 23:21:01 +0200 Subject: [PATCH 485/583] Simplify location of acyclic interval --- .../flowanalysis/StructuralAnalysis.scala | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index b8164106b9..adc4c1887f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -101,13 +101,7 @@ object StructuralAnalysis { var n = post(postCtr) val gPostMap = post.reverse.zipWithIndex.map(ni => (g.get(ni._1), ni._2)).toMap - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion( - g, - gPostMap, - indexedNodes, - indexOf, - immediateDominators.map(kv => (indexOf(kv._1), indexOf(kv._2))).toMap - )(n) + val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, allDominators)(n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -217,9 +211,7 @@ object StructuralAnalysis { private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( graph: G, postOrderTraversal: Map[G#NodeT, Int], - indexedNodes: IndexedSeq[A], - indexOf: Map[A, Int], - immediateDominators: Map[Int, Int] + allDominators: mutable.Map[A, Seq[A]] )(startingNode: A): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] var entry: graph.NodeT = graph.get(startingNode) @@ -256,17 +248,7 @@ object StructuralAnalysis { } def locateProperAcyclicInterval: Option[AcyclicRegionType] = { - val zippedImmediateDominators = immediateDominators.map[(Int, Int)](kv => (kv._2, kv._1)); - var accumulatedDominatedIndexes = Set.empty[Int]; - var newDominatedIndexes = Set(indexOf(n)) - while (newDominatedIndexes.nonEmpty) { - accumulatedDominatedIndexes = accumulatedDominatedIndexes.union(newDominatedIndexes) - newDominatedIndexes = newDominatedIndexes - .flatMap(ndi => zippedImmediateDominators.filter(ndi == _._1).map(_._2)) - .diff(accumulatedDominatedIndexes) - } - - val dominatedNodes = accumulatedDominatedIndexes.map(di => graph.get(indexedNodes(di))) + val dominatedNodes = allDominators.filter(_._2.contains(n.outer)).map(kv => graph.get(kv._1)).toSet ++ Set(n) if (dominatedNodes.size == 1 || !isAcyclic(dominatedNodes) || // Check if no dominated node is reached from an non-dominated node From 2d6a8612bc6abdd63767627c4d1e4eae5147670b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 00:02:17 +0200 Subject: [PATCH 486/583] Simplify path back --- .../flowanalysis/StructuralAnalysis.scala | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index adc4c1887f..5c6ac71e7e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -118,6 +118,7 @@ object StructuralAnalysis { var reachUnder = Set(n) for { m <- g.nodes.outerIterator + if m != n innerM = controlTree.find(m) if innerM.isEmpty || !innerM.get.hasPredecessors if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, strictlyDominates)(m, n) @@ -193,25 +194,21 @@ object StructuralAnalysis { m: A, n: A ): Boolean = { - if (m == n) { - false - } else { - val innerN = graph.get(n) - val nonNFromMTraverser = graph.innerNodeTraverser(graph.get(m), subgraphNodes = _ != innerN) - val predecessorsOfN = innerN.diPredecessors - graph.nodes.exists { innerK => - innerK != innerN && - predecessorsOfN.contains(innerK) && - strictlyDominates(n, innerK.outer) && - nonNFromMTraverser.pathTo(innerK).isDefined - } + val innerN = graph.get(n) + val nonNFromMTraverser = graph.innerNodeTraverser(graph.get(m), subgraphNodes = _ != innerN) + val predecessorsOfN = innerN.diPredecessors + graph.nodes.exists { innerK => + innerK.outer != n && + predecessorsOfN.contains(innerK) && + strictlyDominates(n, innerK.outer) && + nonNFromMTraverser.pathTo(innerK).isDefined } } private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( - graph: G, - postOrderTraversal: Map[G#NodeT, Int], - allDominators: mutable.Map[A, Seq[A]] + graph: G, + postOrderTraversal: Map[G#NodeT, Int], + allDominators: mutable.Map[A, Seq[A]] )(startingNode: A): (A, Option[(AcyclicRegionType, Set[A], A)]) = { var nSet = Set.empty[graph.NodeT] var entry: graph.NodeT = graph.get(startingNode) From aa843baa55dde78acfa0f447a2aeddebe671233d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 00:03:21 +0200 Subject: [PATCH 487/583] Implement rudimentary cycle caching --- .../flowanalysis/StructuralAnalysis.scala | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 5c6ac71e7e..23be0a7437 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -31,6 +31,21 @@ object StructuralAnalysis { var (indexedNodes, indexOf, immediateDominators, allDominators) = computeDominators(g, entry) def strictlyDominates(n: FlowGraphNode, w: FlowGraphNode): Boolean = n != w && allDominators(w).contains(n) + val knownPartOfNoCycle = mutable.Set.empty[FlowGraphNode] + def inCycle(gg: FlowGraph, n: FlowGraphNode): Boolean = { + if (knownPartOfNoCycle.contains(n)) { + false + } else { + val cycleOpt = gg.findCycleContaining(gg.get(n)) + if (cycleOpt.isDefined) { + true + } else { + knownPartOfNoCycle.add(n) + false + } + } + } + var iterations = 0 while (g.order > 1 && iterations < maxIterations) { // Find post order depth first traversal order for nodes @@ -56,6 +71,11 @@ object StructuralAnalysis { post.filterInPlace(r => !subNodes.contains(r)) postCtr = post.indexOf(newRegion) + if (subNodes.forall(knownPartOfNoCycle.contains)) { + knownPartOfNoCycle.add(newRegion) + } + knownPartOfNoCycle.subtractAll(subNodes) + // Replace edges val incomingEdges = currentGraph.edges.filter { e => !subNodes.contains(e.outer.source) && subNodes.contains(e.outer.target) @@ -114,7 +134,7 @@ object StructuralAnalysis { if (nodes.contains(curEntry)) { curEntry = newRegion } - } else { + } else if (inCycle(g, n)) { var reachUnder = Set(n) for { m <- g.nodes.outerIterator @@ -141,6 +161,8 @@ object StructuralAnalysis { } else { postCtr += 1 } + } else { + postCtr += 1 } } From 6aaa5cc3f6b2ead55792497ade72c8ef55a39684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 00:23:52 +0200 Subject: [PATCH 488/583] Simplify replace method --- .../flowanalysis/StructuralAnalysis.scala | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 23be0a7437..ec736cdcec 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -23,8 +23,8 @@ object StructuralAnalysis { private final val maxIterations = 1000 def analyze(graph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { - var g = graph - var sg = graph.asInstanceOf[SuperFlowGraph] + var g: FlowGraph = graph + var sg: SuperFlowGraph = graph.asInstanceOf[SuperFlowGraph] var curEntry = entry var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] @@ -52,16 +52,10 @@ object StructuralAnalysis { var postCtr = 1 val post = mutable.ListBuffer.empty[FlowGraphNode] - def replace( - currentGraph: FlowGraph, - currentSuperGraph: SuperFlowGraph, - subNodes: Set[FlowGraphNode], - entry: FlowGraphNode, - regionType: RegionType - ): (FlowGraph, SuperFlowGraph, Region) = { + def replace(subNodes: Set[FlowGraphNode], entry: FlowGraphNode, regionType: RegionType): Unit = { val newRegion = Region(regionType, subNodes.flatMap(_.nodeIds), entry) - var newGraph: FlowGraph = currentGraph - var newSuperGraph: SuperFlowGraph = currentSuperGraph + var newGraph: FlowGraph = g + var newSuperGraph: SuperFlowGraph = sg // Compact // Note that adding the new region to the graph and superGraph is done anyways since we add edges later @@ -77,10 +71,10 @@ object StructuralAnalysis { knownPartOfNoCycle.subtractAll(subNodes) // Replace edges - val incomingEdges = currentGraph.edges.filter { e => + val incomingEdges = g.edges.toSet[FlowGraph#EdgeT].filter { e => !subNodes.contains(e.outer.source) && subNodes.contains(e.outer.target) } - val outgoingEdges = currentGraph.edges.filter { e => + val outgoingEdges = g.edges.toSet[FlowGraph#EdgeT].filter { e => subNodes.contains(e.outer.source) && !subNodes.contains(e.outer.target) } @@ -112,7 +106,13 @@ object StructuralAnalysis { ) immediateDominators = allDominators.map(kv => (kv._1, kv._2.head)) - (newGraph, newSuperGraph, newRegion) + // Update graph state + g = newGraph + sg = newSuperGraph + controlTree = controlTree.concat(subNodes.map(node => DiEdge(newRegion, node))) + if (subNodes.contains(curEntry)) { + curEntry = newRegion + } } PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry) { post.append } @@ -125,15 +125,7 @@ object StructuralAnalysis { n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get - - val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, arType) - g = newGraph - sg = newSuperGraph - controlTree = controlTree.concat(nodes.map(node => DiEdge(newRegion, node))) - - if (nodes.contains(curEntry)) { - curEntry = newRegion - } + replace(nodes, entry, arType) } else if (inCycle(g, n)) { var reachUnder = Set(n) for { @@ -149,15 +141,7 @@ object StructuralAnalysis { val cyclicRegionOpt = locateCyclicRegion(g, n, reachUnder) if (cyclicRegionOpt.isDefined) { val (crType, nodes, entry) = cyclicRegionOpt.get - - val (newGraph, newSuperGraph, newRegion) = replace(g, sg, nodes, entry, crType) - g = newGraph - sg = newSuperGraph - controlTree = controlTree.concat(nodes.map(node => DiEdge(newRegion, node))) - - if (nodes.contains(curEntry)) { - curEntry = newRegion - } + replace(nodes, entry, crType) } else { postCtr += 1 } From ff7575f7e278d65c8fee75bd26fad42fe7b30f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 00:55:51 +0200 Subject: [PATCH 489/583] Use mutable graphs in structural analysis --- .../flowanalysis/StructuralAnalysis.scala | 89 +++++++++++-------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index ec736cdcec..32e1d05f31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -11,9 +11,12 @@ import scala.collection.mutable import org.opalj.graphs.DominatorTree import scalax.collection.OneOrMore +import scalax.collection.OuterEdge import scalax.collection.edges.DiEdge +import scalax.collection.generic.Edge import scalax.collection.hyperedges.DiHyperEdge import scalax.collection.immutable.Graph +import scalax.collection.mutable.{Graph => MutableGraph} /** * @author Maximilian Rüsch @@ -22,21 +25,25 @@ object StructuralAnalysis { private final val maxIterations = 1000 + private type MFlowGraph = MutableGraph[FlowGraphNode, DiEdge[FlowGraphNode]] + private type MControlTree = MutableGraph[FlowGraphNode, DiEdge[FlowGraphNode]] + private type MSuperFlowGraph = MutableGraph[FlowGraphNode, Edge[FlowGraphNode]] + def analyze(graph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { - var g: FlowGraph = graph - var sg: SuperFlowGraph = graph.asInstanceOf[SuperFlowGraph] + val g: MFlowGraph = MutableGraph.from(graph.edges.outerIterable) + val sg: MSuperFlowGraph = MutableGraph.from(graph.edges.outerIterable).asInstanceOf[MSuperFlowGraph] var curEntry = entry - var controlTree = Graph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] + val controlTree: MControlTree = MutableGraph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] var (indexedNodes, indexOf, immediateDominators, allDominators) = computeDominators(g, entry) def strictlyDominates(n: FlowGraphNode, w: FlowGraphNode): Boolean = n != w && allDominators(w).contains(n) val knownPartOfNoCycle = mutable.Set.empty[FlowGraphNode] - def inCycle(gg: FlowGraph, n: FlowGraphNode): Boolean = { + def inCycle(n: FlowGraphNode): Boolean = { if (knownPartOfNoCycle.contains(n)) { false } else { - val cycleOpt = gg.findCycleContaining(gg.get(n)) + val cycleOpt = g.findCycleContaining(g.get(n)) if (cycleOpt.isDefined) { true } else { @@ -54,8 +61,6 @@ object StructuralAnalysis { def replace(subNodes: Set[FlowGraphNode], entry: FlowGraphNode, regionType: RegionType): Unit = { val newRegion = Region(regionType, subNodes.flatMap(_.nodeIds), entry) - var newGraph: FlowGraph = g - var newSuperGraph: SuperFlowGraph = sg // Compact // Note that adding the new region to the graph and superGraph is done anyways since we add edges later @@ -71,21 +76,26 @@ object StructuralAnalysis { knownPartOfNoCycle.subtractAll(subNodes) // Replace edges - val incomingEdges = g.edges.toSet[FlowGraph#EdgeT].filter { e => + val incomingEdges = g.edges.filter { e => !subNodes.contains(e.outer.source) && subNodes.contains(e.outer.target) } - val outgoingEdges = g.edges.toSet[FlowGraph#EdgeT].filter { e => + val outgoingEdges = g.edges.filter { e => subNodes.contains(e.outer.source) && !subNodes.contains(e.outer.target) } - newGraph ++= incomingEdges.map(e => DiEdge(e.outer.source, newRegion)) - .concat(outgoingEdges.map(e => DiEdge(newRegion, e.outer.target))) - newGraph = newGraph.removedAll(subNodes, Set.empty) - - newSuperGraph ++= incomingEdges.map(e => DiEdge(e.outer.source, newRegion)) - .concat(outgoingEdges.map(e => DiEdge(newRegion, e.outer.target))) - newSuperGraph --= incomingEdges.concat(outgoingEdges).map(e => DiEdge(e.outer.source, e.outer.target)) - .concat(Seq(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get))) + val newRegionEdges = incomingEdges.map { e => + OuterEdge[FlowGraphNode, DiEdge[FlowGraphNode]](DiEdge(e.outer.source, newRegion)) + }.concat(outgoingEdges.map { e => + OuterEdge[FlowGraphNode, DiEdge[FlowGraphNode]](DiEdge(newRegion, e.outer.target)) + }) + g.addAll(newRegionEdges) + g.removeAll(subNodes, Set.empty) + + sg.addAll(newRegionEdges) + sg.removeAll { + incomingEdges.concat(outgoingEdges).map(e => DiEdge(e.outer.source, e.outer.target)) + .concat(Seq(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get))) + } // Update dominator data indexedNodes = indexedNodes.filterNot(subNodes.contains).appended(newRegion) @@ -106,39 +116,41 @@ object StructuralAnalysis { ) immediateDominators = allDominators.map(kv => (kv._1, kv._2.head)) - // Update graph state - g = newGraph - sg = newSuperGraph - controlTree = controlTree.concat(subNodes.map(node => DiEdge(newRegion, node))) + // Update remaining graph state + controlTree.addAll(subNodes.map(node => + OuterEdge[FlowGraphNode, DiEdge[FlowGraphNode]](DiEdge(newRegion, node)) + )) if (subNodes.contains(curEntry)) { curEntry = newRegion } } - PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, FlowGraph](g, curEntry) { post.append } + PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, MFlowGraph](g, curEntry) { post.append } while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) - val gPostMap = post.reverse.zipWithIndex.map(ni => (g.get(ni._1), ni._2)).toMap - val (newStartingNode, acyclicRegionOpt) = locateAcyclicRegion(g, gPostMap, allDominators)(n) + val gPostMap = + post.reverse.zipWithIndex.map(ni => (g.get(ni._1).asInstanceOf[MFlowGraph#NodeT], ni._2)).toMap + val (newStartingNode, acyclicRegionOpt) = + locateAcyclicRegion[FlowGraphNode, MFlowGraph](g, gPostMap, allDominators)(n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get replace(nodes, entry, arType) - } else if (inCycle(g, n)) { + } else if (inCycle(n)) { var reachUnder = Set(n) for { m <- g.nodes.outerIterator if m != n innerM = controlTree.find(m) if innerM.isEmpty || !innerM.get.hasPredecessors - if StructuralAnalysis.pathBack[FlowGraphNode, FlowGraph](g, strictlyDominates)(m, n) + if StructuralAnalysis.pathBack[FlowGraphNode, MFlowGraph](g, strictlyDominates)(m, n) } { reachUnder = reachUnder.incl(m) } - val cyclicRegionOpt = locateCyclicRegion(g, n, reachUnder) + val cyclicRegionOpt = locateCyclicRegion[FlowGraphNode, MFlowGraph](g, n, reachUnder) if (cyclicRegionOpt.isDefined) { val (crType, nodes, entry) = cyclicRegionOpt.get replace(nodes, entry, crType) @@ -157,10 +169,14 @@ object StructuralAnalysis { throw new IllegalStateException(s"Could not reduce tree in $maxIterations iterations!") } - (g, sg, controlTree) + ( + Graph.from(g.edges.outerIterable), + Graph.from(sg.edges.outerIterable), + Graph.from(controlTree.edges.outerIterable) + ) } - private def computeDominators[A, G <: Graph[A, DiEdge[A]]]( + private def computeDominators[A, G <: MutableGraph[A, DiEdge[A]]]( graph: G, entry: A ): (IndexedSeq[A], Map[A, Int], mutable.Map[A, A], mutable.Map[A, Seq[A]]) = { @@ -196,7 +212,7 @@ object StructuralAnalysis { (outerIndexedNodes, indexOf.map(kv => (kv._1.outer, kv._2)), immediateDominators, allDominators) } - private def pathBack[A, G <: Graph[A, DiEdge[A]]](graph: G, strictlyDominates: (A, A) => Boolean)( + private def pathBack[A, G <: MutableGraph[A, DiEdge[A]]](graph: G, strictlyDominates: (A, A) => Boolean)( m: A, n: A ): Boolean = { @@ -211,7 +227,7 @@ object StructuralAnalysis { } } - private def locateAcyclicRegion[A <: FlowGraphNode, G <: Graph[A, DiEdge[A]]]( + private def locateAcyclicRegion[A <: FlowGraphNode, G <: MutableGraph[A, DiEdge[A]]]( graph: G, postOrderTraversal: Map[G#NodeT, Int], allDominators: mutable.Map[A, Seq[A]] @@ -314,7 +330,7 @@ object StructuralAnalysis { (n.outer, rType.map((_, nSet.map(_.outer), entry))) } - private def locateCyclicRegion[A, G <: Graph[A, DiEdge[A]]]( + private def locateCyclicRegion[A, G <: MutableGraph[A, DiEdge[A]]]( graph: G, startingNode: A, reachUnder: Set[A] @@ -354,19 +370,16 @@ object StructuralAnalysis { object PostOrderTraversal { /** @note This function should be kept stable with regards to an ordering on the given graph nodes. */ - def foreachInTraversalFrom[A, G <: Graph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( + def foreachInTraversalFrom[A, G <: MutableGraph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( implicit ordering: Ordering[A] ): Unit = { - implicit val innerOrdering: Ordering[graph.NodeT] = ordering.on(_.outer) var visited = Set.empty[graph.NodeT] - def foreachInTraversal( - node: graph.NodeT - )(nodeHandler: A => Unit): Unit = { + def foreachInTraversal(node: graph.NodeT)(nodeHandler: A => Unit): Unit = { visited = visited + node for { - successor <- (node.diSuccessors -- visited).toList.sorted + successor <- (node.diSuccessors -- visited).toList.sorted(ordering.on((in: graph.NodeT) => in.outer)) } { foreachInTraversal(successor)(nodeHandler) } From 2feeb21cd6a1918cd0eb03d5f15e9b4fb99c7b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 14:29:40 +0200 Subject: [PATCH 490/583] Simplify dominator updates --- .../string/flowanalysis/StructuralAnalysis.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 32e1d05f31..b468edb4f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -106,11 +106,11 @@ object StructuralAnalysis { allDominators = allDominators.map(kv => ( kv._1, { - val intersection = kv._2.intersect(subNodes.toSeq) - if (intersection.nonEmpty) { - val index = kv._2.indexWhere(intersection.contains) - kv._2.patch(index, Seq(newRegion), intersection.size) - } else kv._2 + val index = kv._2.indexWhere(subNodes.contains) + if (index != -1) + kv._2.patch(index, Seq(newRegion), kv._2.lastIndexWhere(subNodes.contains) - index + 1) + else + kv._2 } ) ) From 862373c82477e964a3619ba5882ca0dd1537bde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 5 Jul 2024 16:24:03 +0200 Subject: [PATCH 491/583] Simplify post order traversal --- .../flowanalysis/StructuralAnalysis.scala | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index b468edb4f4..86cea1d73c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -10,6 +10,8 @@ import scala.collection.mutable import org.opalj.graphs.DominatorTree +import scalax.collection.GraphTraversal.DepthFirst +import scalax.collection.GraphTraversal.Parameters import scalax.collection.OneOrMore import scalax.collection.OuterEdge import scalax.collection.edges.DiEdge @@ -125,7 +127,11 @@ object StructuralAnalysis { } } - PostOrderTraversal.foreachInTraversalFrom[FlowGraphNode, MFlowGraph](g, curEntry) { post.append } + val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) + g.innerNodeDownUpTraverser(g.get(curEntry), Parameters(DepthFirst), ordering = ordering).foreach { + case (down, in) if !down => post.append(in.outer) + case _ => + } while (g.order > 1 && postCtr < post.size) { var n = post(postCtr) @@ -366,27 +372,3 @@ object StructuralAnalysis { } } } - -object PostOrderTraversal { - - /** @note This function should be kept stable with regards to an ordering on the given graph nodes. */ - def foreachInTraversalFrom[A, G <: MutableGraph[A, DiEdge[A]]](graph: G, initial: A)(nodeHandler: A => Unit)( - implicit ordering: Ordering[A] - ): Unit = { - var visited = Set.empty[graph.NodeT] - - def foreachInTraversal(node: graph.NodeT)(nodeHandler: A => Unit): Unit = { - visited = visited + node - - for { - successor <- (node.diSuccessors -- visited).toList.sorted(ordering.on((in: graph.NodeT) => in.outer)) - } { - foreachInTraversal(successor)(nodeHandler) - } - - nodeHandler(node.outer) - } - - foreachInTraversal(graph.get(initial))(nodeHandler) - } -} From 7f6fd1dfa8d9678777468272bd62535e35e7ff3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 6 Jul 2024 12:26:22 +0200 Subject: [PATCH 492/583] Make analyzing proper regions somewhat more efficient --- .../flowanalysis/DataFlowAnalysis.scala | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index c35cf9060b..4bc1cfaceb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -90,25 +90,21 @@ object DataFlowAnalysis { var sortedCurrentNodes = List(entryNode) var currentNodeEnvs = Map((entryNode, pipe(entry, env))) - while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { + var finishedNodeAlternatives = Seq.empty[StringTreeEnvironment] + while (currentNodeEnvs.nonEmpty) { val nextNodeEnvs = sortedCurrentNodes.flatMap { node => - if (node.diSuccessors.isEmpty) { - Iterable((node, currentNodeEnvs(node))) - } else { - node.diSuccessors.toList.sortBy(_.outer).map { successor => - (successor, pipe(successor, currentNodeEnvs(node))) - } - } + node.diSuccessors.map { successor => (successor, pipe(successor, currentNodeEnvs(node))) } } - sortedCurrentNodes = nextNodeEnvs.map(_._1).distinct.sortBy(_.outer) - currentNodeEnvs = nextNodeEnvs.groupBy(_._1) map { kv => + + finishedNodeAlternatives ++= nextNodeEnvs.filterNot(_._1.hasSuccessors).sortBy(_._1.outer).map(_._2) + val unfinishedNextNodeEnvs = nextNodeEnvs.filter(_._1.hasSuccessors) + sortedCurrentNodes = unfinishedNextNodeEnvs.map(_._1).distinct.sortBy(_.outer) + currentNodeEnvs = unfinishedNextNodeEnvs.groupBy(_._1) map { kv => (kv._1, kv._2.head._2.joinMany(kv._2.tail.map(_._2))) } } - sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => - env.join(currentNodeEnvs(nextNode)) - } + finishedNodeAlternatives.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextEnv) => env.join(nextEnv) } } def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { @@ -150,22 +146,22 @@ object DataFlowAnalysis { val entryNode = removedBackEdgesGraph.get(entry) var sortedCurrentNodes = List(entryNode) var currentNodeEnvs = Map((entryNode, pipe(entry, env))) - while (currentNodeEnvs.keys.exists(_.diSuccessors.nonEmpty)) { + var finishedNodeAlternatives = Seq.empty[StringTreeEnvironment] + while (currentNodeEnvs.nonEmpty) { val nextNodeEnvs = sortedCurrentNodes.flatMap { node => - if (node.diSuccessors.isEmpty) { - Iterable((node, currentNodeEnvs(node))) - } else { - node.diSuccessors.toList.sortBy(_.outer).map { successor => - (successor, pipe(successor, currentNodeEnvs(node))) - } - } + node.diSuccessors.map { successor => (successor, pipe(successor, currentNodeEnvs(node))) } + } + + finishedNodeAlternatives ++= nextNodeEnvs.filterNot(_._1.hasSuccessors).sortBy(_._1.outer).map(_._2) + val unfinishedNextNodeEnvs = nextNodeEnvs.filter(_._1.hasSuccessors) + sortedCurrentNodes = unfinishedNextNodeEnvs.map(_._1).distinct.sortBy(_.outer) + currentNodeEnvs = unfinishedNextNodeEnvs.groupBy(_._1) map { kv => + (kv._1, kv._2.head._2.joinMany(kv._2.tail.map(_._2))) } - sortedCurrentNodes = nextNodeEnvs.map(_._1).distinct.sortBy(_.outer) - currentNodeEnvs = nextNodeEnvs.groupMapReduce(_._1)(_._2) { (env, otherEnv) => env.join(otherEnv) } } - val resultEnv = sortedCurrentNodes.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextNode) => - env.join(currentNodeEnvs(nextNode)) + val resultEnv = finishedNodeAlternatives.foldLeft(StringTreeEnvironment(Map.empty)) { + (env, nextEnv) => env.join(nextEnv) } // Looped operations that modify string contents are not supported here From c196d7ec08baace7bb668283f1e784cd09e01363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 6 Jul 2024 15:06:48 +0200 Subject: [PATCH 493/583] Rework data flow analysis of proper regions --- .../string_analysis/l0/L0TestMethods.java | 24 +++---- .../string_analysis/l1/L1TestMethods.java | 6 +- .../properties/string/StringTreeNode.scala | 16 ++--- .../flowanalysis/DataFlowAnalysis.scala | 64 ++++++++----------- 4 files changed, 48 insertions(+), 62 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 88b60b583c..20f6300db6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -363,7 +363,7 @@ public void switchRelevantAndIrrelevant(int value) { @StringDefinitionsCollection( value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac|ad)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|a|ad)") }) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -433,7 +433,7 @@ public void switchRelevantWithRelevantDefault(int value) { @StringDefinitionsCollection( value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|a|af|ac|ad)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|a|ad|af)") }) public void switchNestedNoNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -461,7 +461,7 @@ public void switchNestedNoNestedDefault(int value, int value2) { @StringDefinitionsCollection( value = "Switch statement a relevant default case and a nested switch statement", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|af|ac|ad|ae)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|ad|ae|af)") }) public void switchNestedWithNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -492,8 +492,8 @@ public void switchNestedWithNestedDefault(int value, int value2) { @StringDefinitionsCollection( value = "if-else control structure which append to a string builder with an int expr and an int", stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(^-?\\d+$|x)"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(x|42-42)") + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)"), + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(42-42|x)") }) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb1 = new StringBuilder(); @@ -577,7 +577,7 @@ public void ifElseWithStringBuilder3() { @StringDefinitionsCollection( value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|abcd|axyz)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(abcd|a|axyz)") }) public void ifElseWithStringBuilder4() { StringBuilder sb = new StringBuilder("a"); @@ -822,11 +822,11 @@ public void withException(String filename) { // Exception case without own thrown exception @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====)"), // The following cases are detected: - // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append - // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append - // 3. First append succeeds, throws no exception -> only first append - // 4. First append is executed but throws an exception Throwable -> both appends - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(=====|==========|=====.*|=====.*=====)") + // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 3) + // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 1) + // 3. First append succeeds, throws no exception -> only first append (Pos 4) + // 4. First append is executed but throws an exception Throwable -> both appends (Pos 2) + @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====|=====|=====.*)") }) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); @@ -932,7 +932,7 @@ public void replaceExamples(int value) { ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "", - realisticLevel = DYNAMIC, realisticStrings = ".*" + realisticLevel = DYNAMIC, realisticStrings = "(.*|)" ), @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*", diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 64bada5147..d9a2e370bc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -88,7 +88,7 @@ public void simpleNonVirtualFunctionCallTestWithIf(int i) { @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|ERROR|java.lang.StringBuilder)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)") }) public void initFromNonVirtualFunctionCallTest(int i) { String s; @@ -181,7 +181,7 @@ public void getStaticTest() { @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|It is Hello, World)") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "It is (great|Hello, World)") }) public void appendWithTwoDefSitesWithFuncCallTest(int i) { String s; @@ -271,7 +271,7 @@ public void noCallersInformationRequiredTest(String s) { expectedStrings = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?", realisticLevel = CONSTANT, // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - realisticStrings = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|Hello, World_paintname_AlphaTest|(Hello, World_paintname_PAD|Hello, World_paintname)_AlphaTest|Hello, World_paintname_REFLECT_AlphaTest|Hello, World_paintname_REPEAT_AlphaTest)" + realisticStrings = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest)" ) }) public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 33fd47b4d6..a7f8bdee95 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -116,13 +116,13 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } override def simplify: StringTreeNode = { - val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty).filterNot(_.isInvalid) - nonEmptyChildren.size match { + val validChildren = children.map(_.simplify).filterNot(_.isInvalid) + validChildren.size match { case 0 => StringTreeInvalidElement - case 1 => nonEmptyChildren.head + case 1 => validChildren.head case _ => var newChildren = Seq.empty[StringTreeNode] - nonEmptyChildren.foreach { + validChildren.foreach { case orChild: StringTreeOr => newChildren :++= orChild.children case child => newChildren :+= child } @@ -154,13 +154,13 @@ object StringTreeOr { } def fromNodes(children: StringTreeNode*): StringTreeNode = { - val nonNeutralDistinctChildren = children.distinct.filterNot(_.isEmpty) - nonNeutralDistinctChildren.size match { + val validDistinctChildren = children.filterNot(_.isInvalid).distinct + validDistinctChildren.size match { case 0 => StringTreeInvalidElement - case 1 => nonNeutralDistinctChildren.head + case 1 => validDistinctChildren.head case _ => var newChildren = Seq.empty[StringTreeNode] - nonNeutralDistinctChildren.foreach { + validDistinctChildren.foreach { case orChild: StringTreeOr => newChildren :++= orChild.children case child => newChildren :+= child } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 4bc1cfaceb..58ad0fda95 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -6,10 +6,17 @@ package analyses package string package flowanalysis +import scala.collection.mutable + import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import scalax.collection.GraphTraversal.BreadthFirst +import scalax.collection.GraphTraversal.Parameters +import scalax.collection.generic.Edge +import scalax.collection.immutable.Graph + object DataFlowAnalysis { def compute( @@ -84,27 +91,26 @@ object DataFlowAnalysis { envAfterBranches._1.join(envAfterBranches._2) } - def processProper(entry: FlowGraphNode): StringTreeEnvironment = { - val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) - val entryNode = limitedFlowGraph.get(entry) - - var sortedCurrentNodes = List(entryNode) - var currentNodeEnvs = Map((entryNode, pipe(entry, env))) - var finishedNodeAlternatives = Seq.empty[StringTreeEnvironment] - while (currentNodeEnvs.nonEmpty) { - val nextNodeEnvs = sortedCurrentNodes.flatMap { node => - node.diSuccessors.map { successor => (successor, pipe(successor, currentNodeEnvs(node))) } - } - - finishedNodeAlternatives ++= nextNodeEnvs.filterNot(_._1.hasSuccessors).sortBy(_._1.outer).map(_._2) - val unfinishedNextNodeEnvs = nextNodeEnvs.filter(_._1.hasSuccessors) - sortedCurrentNodes = unfinishedNextNodeEnvs.map(_._1).distinct.sortBy(_.outer) - currentNodeEnvs = unfinishedNextNodeEnvs.groupBy(_._1) map { kv => - (kv._1, kv._2.head._2.joinMany(kv._2.tail.map(_._2))) + def handleProperSubregion[A <: FlowGraphNode, G <: Graph[A, Edge[A]]](g: G, entry: A): StringTreeEnvironment = { + val entryNode = g.get(entry) + val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) + val traverser = entryNode.innerNodeTraverser(Parameters(BreadthFirst)).withOrdering(ordering) + // We know that the graph is acyclic here, so we can be sure that the topological sort never fails + val sortedNodes = traverser.topologicalSort().toOption.get.toSeq + + val currentNodeEnvs = mutable.Map((entryNode, pipe(entry, env))) + for { currentNode <- sortedNodes.filter(_ != entryNode) } { + val previousEnvs = currentNode.diPredecessors.toList.sortBy(_.outer).map { dp => + pipe(currentNode.outer, currentNodeEnvs(dp)) } + currentNodeEnvs.update(currentNode, previousEnvs.head.joinMany(previousEnvs.tail)) } - finishedNodeAlternatives.foldLeft(StringTreeEnvironment(Map.empty)) { (env, nextEnv) => env.join(nextEnv) } + currentNodeEnvs(sortedNodes.last) + } + + def processProper(entry: FlowGraphNode): StringTreeEnvironment = { + handleProperSubregion(superFlowGraph.filter(innerChildNodes.contains), entry) } def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { @@ -143,27 +149,7 @@ object DataFlowAnalysis { env.updateAll(StringTreeDynamicString) } else { // Handle resulting acyclic region - val entryNode = removedBackEdgesGraph.get(entry) - var sortedCurrentNodes = List(entryNode) - var currentNodeEnvs = Map((entryNode, pipe(entry, env))) - var finishedNodeAlternatives = Seq.empty[StringTreeEnvironment] - while (currentNodeEnvs.nonEmpty) { - val nextNodeEnvs = sortedCurrentNodes.flatMap { node => - node.diSuccessors.map { successor => (successor, pipe(successor, currentNodeEnvs(node))) } - } - - finishedNodeAlternatives ++= nextNodeEnvs.filterNot(_._1.hasSuccessors).sortBy(_._1.outer).map(_._2) - val unfinishedNextNodeEnvs = nextNodeEnvs.filter(_._1.hasSuccessors) - sortedCurrentNodes = unfinishedNextNodeEnvs.map(_._1).distinct.sortBy(_.outer) - currentNodeEnvs = unfinishedNextNodeEnvs.groupBy(_._1) map { kv => - (kv._1, kv._2.head._2.joinMany(kv._2.tail.map(_._2))) - } - } - - val resultEnv = finishedNodeAlternatives.foldLeft(StringTreeEnvironment(Map.empty)) { - (env, nextEnv) => env.join(nextEnv) - } - + val resultEnv = handleProperSubregion(removedBackEdgesGraph, entry) // Looped operations that modify string contents are not supported here if (resultEnv != env) env.updateAll(StringTreeDynamicString) else env From 371850ce70fa32c613616503f166b61bad0c597c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 7 Jul 2024 21:23:13 +0200 Subject: [PATCH 494/583] Correct webs computation for data flow analysis start --- .../tac/fpcf/analyses/string/ComputationState.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index e5b14e9ba7..f3b28fa77a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -59,11 +59,12 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs }.toSeq.sortBy(_.defPCs.toList.min).foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => - val index = reducedWebs.indexWhere(_.identifiesSameVarAs(web)) - if (index == -1) + val mappedWebs = reducedWebs.map(w => (w, w.identifiesSameVarAs(web))) + if (!mappedWebs.exists(_._2)) { reducedWebs :+ web - else - reducedWebs.updated(index, reducedWebs(index).combine(web)) + } else { + mappedWebs.filterNot(_._2).map(_._1) :+ mappedWebs.filter(_._2).map(_._1).reduce(_.combine(_)).combine(web) + } }.iterator } From 3088212dcccd7e2d86cfa656831f2cb348cc7051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 7 Jul 2024 21:23:32 +0200 Subject: [PATCH 495/583] Speed up proper region data flow somewhat --- .../string/flowanalysis/DataFlowAnalysis.scala | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 58ad0fda95..706a49ff5c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -91,10 +91,16 @@ object DataFlowAnalysis { envAfterBranches._1.join(envAfterBranches._2) } - def handleProperSubregion[A <: FlowGraphNode, G <: Graph[A, Edge[A]]](g: G, entry: A): StringTreeEnvironment = { + def handleProperSubregion[A <: FlowGraphNode, G <: Graph[A, Edge[A]]]( + g: G, + innerNodes: Set[G#NodeT], + entry: A + ): StringTreeEnvironment = { val entryNode = g.get(entry) val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) - val traverser = entryNode.innerNodeTraverser(Parameters(BreadthFirst)).withOrdering(ordering) + val traverser = entryNode.innerNodeTraverser(Parameters(BreadthFirst)) + .withOrdering(ordering) + .withSubgraph(nodes = innerNodes.contains) // We know that the graph is acyclic here, so we can be sure that the topological sort never fails val sortedNodes = traverser.topologicalSort().toOption.get.toSeq @@ -110,7 +116,7 @@ object DataFlowAnalysis { } def processProper(entry: FlowGraphNode): StringTreeEnvironment = { - handleProperSubregion(superFlowGraph.filter(innerChildNodes.contains), entry) + handleProperSubregion[FlowGraphNode, superFlowGraph.type](superFlowGraph, innerChildNodes, entry) } def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { @@ -149,7 +155,11 @@ object DataFlowAnalysis { env.updateAll(StringTreeDynamicString) } else { // Handle resulting acyclic region - val resultEnv = handleProperSubregion(removedBackEdgesGraph, entry) + val resultEnv = handleProperSubregion[FlowGraphNode, removedBackEdgesGraph.type]( + removedBackEdgesGraph, + removedBackEdgesGraph.nodes.toSet, + entry + ) // Looped operations that modify string contents are not supported here if (resultEnv != env) env.updateAll(StringTreeDynamicString) else env From f63726955796633d88eae0091185b7d40e0167a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 7 Jul 2024 21:23:57 +0200 Subject: [PATCH 496/583] Prefilter string tree concats for empty elements --- .../org/opalj/br/fpcf/properties/string/StringTreeNode.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index a7f8bdee95..e5a2e873c0 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -99,8 +99,10 @@ object StringTreeConcat { def fromNodes(children: StringTreeNode*): StringTreeNode = { if (children.isEmpty || children.exists(_.isInvalid)) { StringTreeInvalidElement + } else if (children.forall(_.isEmpty)) { + StringTreeEmptyConst } else { - new StringTreeConcat(children) + new StringTreeConcat(children.filterNot(_.isEmpty)) } } } From 97901f0ce38e66b19aec368a62ca163e86f972bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 8 Jul 2024 21:58:08 +0200 Subject: [PATCH 497/583] Fix parameter dependency handling in context string analysis --- .../string_analysis/l1/L1TestMethods.java | 9 ++++- .../analyses/string/StringAnalysisState.scala | 36 +++++++++---------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index d9a2e370bc..dc074d6bf4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -218,7 +218,7 @@ public void dependenciesWithinFinalizeTest(String s, Class clazz) { @StringDefinitionsCollection( value = "a function parameter being analyzed on its own", stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Hello, World|my.helper.Class)") }) public String callerWithFunctionParameterTest(String s, float i) { analyzeString(s); @@ -235,12 +235,19 @@ public void belongsToSomeTestCase() { analyzeString(s); } + public void belongsToSomeTestCaseAnotherTime() { + callerWithFunctionParameterTest(belongsToTheSameTestCaseAnotherTime(), 900); + } + /** * Necessary for the callerWithFunctionParameterTest. */ public static String belongsToTheSameTestCase() { return getHelloWorld(); } + public static String belongsToTheSameTestCaseAnotherTime() { + return getHelperClass(); + } @StringDefinitionsCollection( value = "a case where a function takes another function as one of its parameters", diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 92de87d3fe..7dfb56e28a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -57,7 +57,7 @@ private[string] case class ContextStringAnalysisState( // Parameter StringConstancy private val _entityToParamIndexMapping: mutable.Map[VariableContext, (Int, Method)] = mutable.Map.empty - private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[Method, VariableContext]] = + private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[Method, Seq[VariableContext]]] = mutable.Map.empty.withDefaultValue(mutable.Map.empty) private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = mutable.Map.empty @@ -68,22 +68,16 @@ private[string] case class ContextStringAnalysisState( m: Method, dependee: EOptionP[VariableContext, StringConstancyProperty] ): Unit = { - val previousParameterEntity = _paramIndexToEntityMapping(index).put(m, dependee.e) - if (previousParameterEntity.isDefined) { - _entityToParamIndexMapping.remove(previousParameterEntity.get) + if (!_paramIndexToEntityMapping.contains(index)) { + _paramIndexToEntityMapping(index) = mutable.Map.empty } + _paramIndexToEntityMapping(index).put(m, Seq(dependee.e)) _entityToParamIndexMapping(dependee.e) = (index, m) _paramDependees(dependee.e) = dependee } def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = { - if (_entityToParamIndexMapping.contains(dependee.e)) { - _paramDependees(dependee.e) = dependee - } else { - // When there is no parameter index for the dependee entity we lost track of it and do not need it anymore. - // Ensure no update is registered and the dependees are cleared from this EOptionP. - _paramDependees.remove(dependee.e) - } + _paramDependees(dependee.e) = dependee } def registerInvalidParamReference(index: Int, m: Method): Unit = { _invalidParamReferences.updateWith(m)(ov => Some(ov.getOrElse(Set.empty) + index)) @@ -95,15 +89,19 @@ private[string] case class ContextStringAnalysisState( stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap } else { stringTree.collectParameterIndices.map { index => - val paramOptions = _paramIndexToEntityMapping(index).keysIterator.flatMap { m => - if (_invalidParamReferences.contains(m)) { - Some(StringTreeNode.ub) - } else { - val dependee = _paramDependees(_paramIndexToEntityMapping(index)(m)) - if (dependee.hasUBP) Some(dependee.ub.sci.tree) - else None + val paramOptions = _paramIndexToEntityMapping(index).keys.toSeq + .sortBy(_.fullyQualifiedSignature) + .flatMap { m => + if (_invalidParamReferences.contains(m)) { + Some(StringTreeNode.ub) + } else { + _paramIndexToEntityMapping(index)(m) + .sortBy(_.pc) + .map(_paramDependees) + .filter(_.hasUBP) + .map(_.ub.sci.tree) + } } - }.toSeq (index, StringTreeOr(paramOptions).simplify) }.toMap From a03542d828804f1845c82170201d6695807d786f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 11 Jul 2024 13:02:31 +0200 Subject: [PATCH 498/583] Really fix parameter dependency handling and add test case --- .../string_analysis/l1/L1TestMethods.java | 18 ++++++++++++++++++ .../analyses/string/StringAnalysisState.scala | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index dc074d6bf4..d1c72c4cfa 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -249,6 +249,24 @@ public static String belongsToTheSameTestCaseAnotherTime() { return getHelperClass(); } + @StringDefinitionsCollection( + value = "a function parameter being analyzed on its own", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Hello, World|my.helper.Class)") + }) + public String callerWithFunctionParameterMultipleCallsInSameMethodTest(String s, float i) { + analyzeString(s); + return s; + } + + /** + * Necessary for the callerWithFunctionParameterMultipleCallsInSameMethodTest. + */ + public void belongsToSomeTestCase3() { + callerWithFunctionParameterMultipleCallsInSameMethodTest(belongsToTheSameTestCase(), 900); + callerWithFunctionParameterMultipleCallsInSameMethodTest(belongsToTheSameTestCaseAnotherTime(), 900); + } + @StringDefinitionsCollection( value = "a case where a function takes another function as one of its parameters", stringDefinitions = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 7dfb56e28a..901d9366a3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -72,7 +72,10 @@ private[string] case class ContextStringAnalysisState( _paramIndexToEntityMapping(index) = mutable.Map.empty } - _paramIndexToEntityMapping(index).put(m, Seq(dependee.e)) + _paramIndexToEntityMapping(index).updateWith(m) { + case None => Some(Seq(dependee.e)) + case Some(previous) => Some(previous :+ dependee.e) + } _entityToParamIndexMapping(dependee.e) = (index, m) _paramDependees(dependee.e) = dependee } From 0936496f4b24f6db8a7d3fe8d91b56fb18ec7832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 11 Jul 2024 14:17:30 +0200 Subject: [PATCH 499/583] Add soundness mode and failure to string interpretation --- .../string_analysis/l0/L0TestMethods.java | 60 +++++++++++++++++++ .../string_analysis/l0/StringProvider.java | 3 + .../string_analysis/l1/L1TestMethods.java | 57 ++++++++++++++---- .../AllowedSoundnessModes.java | 15 +++++ .../string_analysis/SoundnessMode.java | 21 +++++++ .../org/opalj/fpcf/StringAnalysisTest.scala | 51 ++++++++++++++++ OPAL/tac/src/main/resources/reference.conf | 5 +- .../analyses/string/StringInterpreter.scala | 21 ++++--- .../InterpretationHandler.scala | 23 +++++++ .../string/interpretation/SoundnessMode.scala | 27 +++++++++ .../L0InterpretationHandler.scala | 10 ++-- .../L0NonVirtualFunctionCallInterpreter.scala | 8 ++- .../L0StaticFunctionCallInterpreter.scala | 10 ++-- .../L0VirtualFunctionCallInterpreter.scala | 11 ++-- .../analyses/string/l1/L1StringAnalysis.scala | 4 +- .../L1FieldReadInterpreter.scala | 33 +++++----- .../L1InterpretationHandler.scala | 8 +-- .../L1VirtualFunctionCallInterpreter.scala | 11 ++-- 18 files changed, 315 insertions(+), 63 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java index 20f6300db6..2ed6637b44 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_analysis.l0; +import org.opalj.fpcf.properties.string_analysis.AllowedSoundnessModes; +import org.opalj.fpcf.properties.string_analysis.SoundnessMode; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -194,6 +196,18 @@ public void stringBuilderBufferInitArguments() { analyzeString(sb2.toString()); } + @AllowedSoundnessModes(SoundnessMode.LOW) + @StringDefinitionsCollection( + value = "at this point, function call cannot be handled => DYNAMIC", + stringDefinitions = { + @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) + }) + public void fromFunctionCallLowSoundness() { + String className = getStringBuilderClassName(); + analyzeString(className); + } + + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "at this point, function call cannot be handled => DYNAMIC", stringDefinitions = { @@ -204,6 +218,20 @@ public void fromFunctionCall() { analyzeString(className); } + @AllowedSoundnessModes(SoundnessMode.LOW) + @StringDefinitionsCollection( + value = "constant string + string from function call => PARTIALLY_CONSTANT", + stringDefinitions = { + @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) + }) + public void fromConstantAndFunctionCallLowSoundness() { + String className = "java.lang."; + System.out.println(className); + className += getSimpleStringBuilderClassName(); + analyzeString(className); + } + + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "constant string + string from function call => PARTIALLY_CONSTANT", stringDefinitions = { @@ -216,6 +244,28 @@ public void fromConstantAndFunctionCall() { analyzeString(className); } + @AllowedSoundnessModes(SoundnessMode.LOW) + @StringDefinitionsCollection( + value = "array access with unknown index", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", + realisticLevel = INVALID, + realisticStrings = StringDefinitions.INVALID_FLOW + ) + }) + public void fromStringArrayLowSoundness(int index) { + String[] classes = { + "java.lang.String", "java.lang.StringBuilder", + "java.lang.System", "java.lang.Runnable" + }; + if (index >= 0 && index < classes.length) { + analyzeString(classes[index]); + } + } + + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "array access with unknown index", stringDefinitions = { @@ -236,6 +286,7 @@ public void fromStringArray(int index) { } } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a case where an array access needs to be interpreted with multiple static and virtual function calls", stringDefinitions = { @@ -302,6 +353,7 @@ public void ternaryOperators(boolean flag, String param) { analyzeString(flag ? s1 + param : s2); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", @@ -743,6 +795,7 @@ public void whileWithBreak(int i) { analyzeString(sb.toString()); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "an extensive example with many control structures", stringDefinitions = { @@ -783,6 +836,7 @@ public void extensive(boolean cond) { analyzeString(sb.toString()); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "an example with a throw (and no try-catch-finally)", stringDefinitions = { @@ -795,6 +849,7 @@ public void withThrow(String filename) throws IOException { analyzeString(sb.toString()); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "case with a try-finally exception", // Multiple string definition values are necessary because the three-address code contains multiple calls to @@ -815,6 +870,7 @@ public void withException(String filename) { } } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "case with a try-catch-finally exception", stringDefinitions = { @@ -840,6 +896,7 @@ public void tryCatchFinally(String filename) { } } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "case with a try-catch-finally throwable", stringDefinitions = { @@ -1071,6 +1128,7 @@ public void simpleSecondStringBuilderRead(String className) { analyzeString(sb1.toString()); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a test case which tests the interpretation of String#valueOf", stringDefinitions = { @@ -1084,6 +1142,7 @@ public void valueOfTest() { analyzeString(String.valueOf(getRuntimeClassName())); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "an example that uses a non final field", stringDefinitions = { @@ -1237,6 +1296,7 @@ protected void setDebugFlags(String[] var1) { } } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "an example with an unknown character read", stringDefinitions = { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java index 8d6263ed2f..b6835bbe5c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java @@ -24,4 +24,7 @@ public static String getFQClassNameWithStringBuilder(String packageName, String return (new StringBuilder()).append(packageName).append(".").append(className).toString(); } + public static String getSomeValue() { + return "someValue"; + } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index d1c72c4cfa..3e9588b245 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -1,13 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_analysis.l1; +import org.opalj.fpcf.fixtures.string_analysis.l0.StringProvider; import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.HelloGreeting; import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; -import org.opalj.fpcf.properties.string_analysis.AllowedDomainLevels; -import org.opalj.fpcf.properties.string_analysis.DomainLevel; -import org.opalj.fpcf.properties.string_analysis.StringDefinitions; -import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; +import org.opalj.fpcf.properties.string_analysis.*; import javax.management.remote.rmi.RMIServer; import java.io.File; @@ -103,6 +101,18 @@ public void initFromNonVirtualFunctionCallTest(int i) { analyzeString(sb.toString()); } + @AllowedSoundnessModes(SoundnessMode.LOW) + @StringDefinitionsCollection( + value = "a case where a static method is called that returns a string but are not " + + "within this project => cannot / will not be interpret", + stringDefinitions = { + @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW), + }) + public void staticMethodOutOfScopeLowSoundnessTest() { + analyzeString(System.clearProperty("os.version")); + } + + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a case where a static method is called that returns a string but are not " + "within this project => cannot / will not be interpret", @@ -138,15 +148,15 @@ public void methodOutOfScopeTest() throws FileNotFoundException { value = "a case where function calls are involved in append operations", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "classname:StringBuilder,osname:.*" + expectedLevel = CONSTANT, + expectedStrings = "classname:StringBuilder,osname:someValue" ) }) public void appendTest() { StringBuilder sb = new StringBuilder("classname:"); sb.append(getSimpleStringBuilderClassName()); sb.append(",osname:"); - sb.append(System.clearProperty("os.name:")); + sb.append(StringProvider.getSomeValue()); analyzeString(sb.toString()); } @@ -169,6 +179,7 @@ public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", stringDefinitions = { @@ -193,6 +204,7 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a case taken from com.sun.javafx.property.PropertyReference#reflect where " + "a dependency within the finalize procedure is present", @@ -355,13 +367,21 @@ public void fieldWithNoWriteTest() { analyzeString(noWriteField); } + @AllowedSoundnessModes(SoundnessMode.LOW) @StringDefinitionsCollection( value = "a case where a field is read whose type is not supported", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = ".*" - ) + @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) + }) + public void nonSupportedFieldTypeReadLowSoundness() { + analyzeString(myObject.toString()); + } + + @AllowedSoundnessModes(SoundnessMode.HIGH) + @StringDefinitionsCollection( + value = "a case where a field is read whose type is not supported", + stringDefinitions = { + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") }) public void nonSupportedFieldTypeRead() { analyzeString(myObject.toString()); @@ -522,6 +542,7 @@ public void getStaticFieldTest() { analyzeString(someKey); } + @AllowedSoundnessModes(SoundnessMode.HIGH) @StringDefinitionsCollection( value = "a case where a String array field is read", stringDefinitions = { @@ -569,6 +590,13 @@ public void valueOfTest() {} }) public void nonFinalFieldRead() {} + @StringDefinitionsCollection( + value = "can handle virtual function calls", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") + }) + public void fromFunctionCallLowSoundness() {} + @StringDefinitionsCollection( value = "can handle virtual function calls", stringDefinitions = { @@ -576,6 +604,13 @@ public void nonFinalFieldRead() {} }) public void fromFunctionCall() {} + @StringDefinitionsCollection( + value = "constant string + string from function call => CONSTANT", + stringDefinitions = { + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") + }) + public void fromConstantAndFunctionCallLowSoundness() {} + @StringDefinitionsCollection( value = "constant string + string from function call => CONSTANT", stringDefinitions = { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java new file mode 100644 index 0000000000..ee0eefa867 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java @@ -0,0 +1,15 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import java.lang.annotation.*; + +/** + * @author Maximilian Rüsch + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface AllowedSoundnessModes { + + SoundnessMode[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java new file mode 100644 index 0000000000..2e3d41f8e5 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +/** + * @author Maximilian Rüsch + */ +public enum SoundnessMode { + + LOW("LOW"), + HIGH("HIGH"); + + private final String value; + + SoundnessMode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} 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 c57f7335f3..c295ccf3ea 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 @@ -4,6 +4,9 @@ package fpcf import java.net.URL +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory + import org.opalj.ai.domain.l1.DefaultDomainWithCFGAndDefUse import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse import org.opalj.ai.fpcf.properties.AIDomainFactoryKey @@ -17,6 +20,7 @@ import org.opalj.br.analyses.Project import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.properties.string_analysis.DomainLevel +import org.opalj.fpcf.properties.string_analysis.SoundnessMode import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV import org.opalj.tac.TACMethodParameter @@ -26,6 +30,7 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.VariableContext +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.analyses.systemproperties.EagerSystemPropertiesAnalysisScheduler @@ -37,6 +42,17 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { def level: Int def domainLevel: DomainLevel + def soundnessMode: SoundnessMode + + override def createConfig(): Config = { + val highSoundness = soundnessMode match { + case SoundnessMode.HIGH => true + case SoundnessMode.LOW => false + } + + super.createConfig() + .withValue(InterpretationHandler.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + } override final def init(p: Project[URL]): Unit = { val domain = domainLevel match { @@ -101,6 +117,12 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { r.isEmpty || r.get } } + .filter { + _._1.runtimeInvisibleAnnotations.forall { a => + val r = isAllowedSoundnessMode(a) + r.isEmpty || r.get + } + } .foldLeft(Seq.empty[(VariableContext, Method)]) { (entities, m) => entities ++ extractPUVars(tacProvider(m._1)).map(e => (VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m._1))), m._2) @@ -150,6 +172,15 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } } } + + def isAllowedSoundnessMode(a: Annotation): Option[Boolean] = { + if (a.annotationType.toJava != "org.opalj.fpcf.properties.string_analysis.AllowedSoundnessModes") None + else Some { + a.elementValuePairs.head.value.asArrayValue.values.exists { v => + SoundnessMode.valueOf(v.asEnumValue.constName) == soundnessMode + } + } + } } object StringAnalysisTest { @@ -229,6 +260,7 @@ sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { class L0StringAnalysisWithL1DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW describe("using the l1 default domain") { runL0Tests() } } @@ -236,10 +268,19 @@ class L0StringAnalysisWithL1DefaultDomainTest extends L0StringAnalysisTest { class L0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW describe("using the l2 default domain") { runL0Tests() } } +class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.HIGH + + describe("using the l2 default domain and the high soundness mode") { runL0Tests() } +} + /** * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis]] works correctly with * respect to some well-defined tests. @@ -281,6 +322,7 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW describe("using the l1 default domain") { runL1Tests() } } @@ -288,6 +330,15 @@ class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW describe("using the l2 default domain") { runL1Tests() } } + +class HighSoundnessL1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.HIGH + + describe("using the l2 default domain and the high soundness mode") { runL1Tests() } +} diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index f2c3e0eb90..cc240c1552 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1494,7 +1494,10 @@ org.opalj { }, cg.reflection.ReflectionRelatedCallsAnalysis.highSoundness = "" // e.g. "all" or "class,method", fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, - string_analysis.l1.L1StringAnalysis.fieldWriteThreshold = 100 + string { + InterpretationHandler.highSoundness = false, + l1.L1StringAnalysis.fieldWriteThreshold = 100 + } } }, tac.cg { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index fb6b0a35e0..e7db95a3c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -9,6 +9,7 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -26,11 +27,8 @@ trait StringInterpreter { */ def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult - def computeFinalLBFor(v: V)(implicit state: InterpretationState): Result = - StringInterpreter.computeFinalLBFor(v) - - def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = - StringInterpreter.computeFinalLBFor(v) + def failure(v: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + StringInterpreter.failure(v) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = StringInterpreter.computeFinalResult(web, sff) @@ -44,11 +42,16 @@ trait StringInterpreter { object StringInterpreter { - def computeFinalLBFor(v: V)(implicit state: InterpretationState): Result = - computeFinalLBFor(v.toPersistentForm(state.tac.stmts)) + def failure(v: V)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + failure(v.toPersistentForm(state.tac.stmts)) - def computeFinalLBFor(v: PV)(implicit state: InterpretationState): Result = - computeFinalResult(StringFlowFunctionProperty.lb(state.pc, v)) + def failure(pv: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = { + if (soundnessMode.isHigh) { + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, pv)) + } else { + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, pv)) + } + } def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(web, sff))) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index c2cf1a44cb..ad62b18309 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -14,6 +14,9 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.SomeEPS +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -28,6 +31,24 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ abstract class InterpretationHandler extends FPCFAnalysis { + private final val ConfigLogCategory = "analysis configuration - string flow interpretation analysis" + + implicit val soundnessMode: SoundnessMode = { + val mode = + try { + SoundnessMode(project.config.getBoolean(InterpretationHandler.SoundnessModeConfigKey)) + } catch { + case t: Throwable => + logOnce { + Error(ConfigLogCategory, s"couldn't read: ${InterpretationHandler.SoundnessModeConfigKey}", t) + } + SoundnessMode(false) + } + + logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) + mode + } + def analyze(entity: MethodPC): ProperPropertyComputationResult = { val tacaiEOptP = ps(entity.dm.definedMethod, TACAI.key) implicit val state: InterpretationState = InterpretationState(entity.pc, entity.dm, tacaiEOptP) @@ -79,6 +100,8 @@ abstract class InterpretationHandler extends FPCFAnalysis { object InterpretationHandler { + final val SoundnessModeConfigKey = "org.opalj.tac.analyses.string.InterpretationHandler.highSoundness" + def getEntity(implicit state: InterpretationState): MethodPC = MethodPC(state.pc, state.dm) /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala new file mode 100644 index 0000000000..b8293b3459 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package interpretation + +trait SoundnessMode { + + def isHigh: Boolean +} + +object SoundnessMode { + + def apply(high: Boolean): SoundnessMode = if (high) HighSoundness else LowSoundness +} + +object LowSoundness extends SoundnessMode { + + override def isHigh: Boolean = false +} + +object HighSoundness extends SoundnessMode { + + override def isHigh: Boolean = true +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 56f90492de..f70a85833f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -29,16 +29,16 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) // Currently unsupported - case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) - case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) + case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.failure(target) case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + new L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => - L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + new L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) @@ -55,7 +55,7 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) case Assignment(_, target, _) => - StringInterpreter.computeFinalLBFor(target) + StringInterpreter.failure(target) case ReturnValue(pc, expr) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala index 07591c9a2d..7464112bd9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI /** @@ -18,8 +19,9 @@ import org.opalj.tac.fpcf.properties.TACAI * @author Maximilian Rüsch */ case class L0NonVirtualFunctionCallInterpreter()( - implicit val p: SomeProject, - implicit val ps: PropertyStore + implicit val p: SomeProject, + implicit val ps: PropertyStore, + implicit val soundnessMode: SoundnessMode ) extends AssignmentLikeBasedStringInterpreter with L0FunctionCallInterpreter { @@ -33,7 +35,7 @@ case class L0NonVirtualFunctionCallInterpreter()( val target = expr.receiver.asVar.toPersistentForm(state.tac.stmts) val calleeMethod = expr.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalLBFor(target) + return failure(target) } val m = calleeMethod.value diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala index 356144beab..9cd3cc3424 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala @@ -12,9 +12,9 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction -import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** @@ -24,7 +24,8 @@ case class L0StaticFunctionCallInterpreter()( implicit override val p: SomeProject, override val ps: PropertyStore, - override val project: SomeProject + override val project: SomeProject, + val soundnessMode: SoundnessMode ) extends AssignmentBasedStringInterpreter with L0ArbitraryStaticFunctionCallInterpreter with L0StringValueOfFunctionCallInterpreter @@ -43,7 +44,7 @@ case class L0StaticFunctionCallInterpreter()( if call.descriptor.returnType == ObjectType.String || call.descriptor.returnType == ObjectType.Object => interpretArbitraryCall(target, call) - case _ => computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + case _ => failure(target) } } } @@ -53,6 +54,7 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter with L0FunctionCallInterpreter { implicit val p: SomeProject + implicit val soundnessMode: SoundnessMode override type E <: StaticFunctionCall[V] override type CallState = FunctionCallState @@ -62,7 +64,7 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter ): ProperPropertyComputationResult = { val calleeMethod = call.resolveCallTarget(state.dm.definedMethod.classFile.thisType) if (calleeMethod.isEmpty) { - return computeFinalLBFor(target) + return failure(target) } val m = calleeMethod.value diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala index 1d64cea886..75da830772 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala @@ -21,6 +21,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue @@ -30,8 +31,9 @@ import org.opalj.value.TheIntegerValue * * @author Maximilian Rüsch */ -case class L0VirtualFunctionCallInterpreter() - extends AssignmentLikeBasedStringInterpreter +class L0VirtualFunctionCallInterpreter( + implicit val soundnessMode: SoundnessMode +) extends AssignmentLikeBasedStringInterpreter with L0ArbitraryVirtualFunctionCallInterpreter with L0AppendCallInterpreter with L0SubstringCallInterpreter { @@ -97,10 +99,11 @@ case class L0VirtualFunctionCallInterpreter() private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { + implicit val soundnessMode: SoundnessMode + protected def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState - ): ProperPropertyComputationResult = - computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + ): ProperPropertyComputationResult = failure(target) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 9283d78096..10e6d6cf2b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -21,9 +21,7 @@ import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHand */ object L1StringAnalysis { - private[l1] final val FieldWriteThresholdConfigKey = { - "org.opalj.fpcf.analyses.string_analysis.l1.L1StringAnalysis.fieldWriteThreshold" - } + private[l1] final val ConfigLogCategory = "analysis configuration - l1 string analysis" } object LazyL1StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala index 02c2ae8fe0..a2c298592a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala @@ -28,8 +28,10 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.log.Error import org.opalj.log.Info +import org.opalj.log.LogContext import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -39,37 +41,38 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * * @author Maximilian Rüsch */ -case class L1FieldReadInterpreter( - ps: PropertyStore, - project: SomeProject, +class L1FieldReadInterpreter( + implicit val ps: PropertyStore, + implicit val project: SomeProject, implicit val declaredFields: DeclaredFields, - implicit val contextProvider: ContextProvider + implicit val contextProvider: ContextProvider, + implicit val soundnessMode: SoundnessMode ) extends AssignmentBasedStringInterpreter { override type E = FieldRead[V] + private final val FieldWriteThresholdConfigKey = { + "org.opalj.fpcf.analyses.string.l1.L1StringAnalysis.fieldWriteThreshold" + } + /** * 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. */ private val fieldWriteThreshold = { + implicit val logContext: LogContext = project.logContext val threshold = try { - project.config.getInt(L1StringAnalysis.FieldWriteThresholdConfigKey) + project.config.getInt(FieldWriteThresholdConfigKey) } catch { case t: Throwable => - logOnce(Error( - "analysis configuration - l1 string analysis", - s"couldn't read: ${L1StringAnalysis.FieldWriteThresholdConfigKey}", - t - ))(project.logContext) + logOnce { + Error(L1StringAnalysis.ConfigLogCategory, s"couldn't read: $FieldWriteThresholdConfigKey", t) + } 10 } - logOnce(Info( - "analysis configuration - l1 string analysis", - "l1 string analysis uses a field write threshold of " + threshold - ))(project.logContext) + logOnce(Info(L1StringAnalysis.ConfigLogCategory, "uses a field write threshold of " + threshold)) threshold } @@ -113,7 +116,7 @@ case class L1FieldReadInterpreter( state: InterpretationState ): ProperPropertyComputationResult = { if (!InterpretationHandler.isSupportedType(fieldRead.declaredFieldType)) { - return computeFinalLBFor(target) + return failure(target) } val field = declaredFields(fieldRead.declaringClass, fieldRead.name, fieldRead.declaredFieldType) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 0896cb07c5..8fad74cd5e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -30,7 +30,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class L1InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { - val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) + implicit val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) override protected def processStatement(implicit @@ -40,10 +40,10 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) // Currently unsupported - case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.computeFinalLBFor(target) + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) case stmt @ Assignment(_, _, expr: FieldRead[V]) => - L1FieldReadInterpreter(ps, p, declaredFields, contextProvider).interpretExpr(stmt, expr) + new L1FieldReadInterpreter().interpretExpr(stmt, expr) // Field reads without result usage are irrelevant case ExprStmt(_, _: FieldRead[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) @@ -82,7 +82,7 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) case Assignment(_, target, _) => - StringInterpreter.computeFinalLBFor(target) + StringInterpreter.failure(target) case ReturnValue(pc, expr) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index d05887a517..667f71484d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -22,6 +22,7 @@ import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0SystemPropertiesInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter @@ -35,9 +36,10 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter( - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider, - implicit val project: SomeProject + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider, + implicit val project: SomeProject, + override implicit val soundnessMode: SoundnessMode ) extends L0VirtualFunctionCallInterpreter with StringInterpreter with L0SystemPropertiesInterpreter @@ -60,6 +62,7 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends L0Functi implicit val ps: PropertyStore implicit val contextProvider: ContextProvider + implicit val soundnessMode: SoundnessMode override type CallState = CalleeDepender @@ -103,7 +106,7 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends L0Functi val newMethods = getNewMethodsFromCallees(callState.methodContext, c)(state, callState) if (newMethods.isEmpty && eps.isFinal) { // Improve add previous results back - computeFinalLBFor(callState.target)(state) + failure(callState.target)(state, soundnessMode) } else { for { method <- newMethods From d9ce2f379f19a48aacf30e4df748a72ae8023502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 21 Jul 2024 18:21:51 +0200 Subject: [PATCH 500/583] Fix soundness mode config key for failures --- .../analyses/string/interpretation/InterpretationHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index ad62b18309..5f887566bf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -100,7 +100,7 @@ abstract class InterpretationHandler extends FPCFAnalysis { object InterpretationHandler { - final val SoundnessModeConfigKey = "org.opalj.tac.analyses.string.InterpretationHandler.highSoundness" + final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.InterpretationHandler.highSoundness" def getEntity(implicit state: InterpretationState): MethodPC = MethodPC(state.pc, state.dm) From c77c3e457c6057cd1f98e7b378909f2b69d01a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 22 Jul 2024 20:39:57 +0200 Subject: [PATCH 501/583] Remove unnecessary statements --- .../org/opalj/tac/fpcf/analyses/string/ComputationState.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index f3b28fa77a..1c98fd8b82 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -58,7 +58,7 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.values.flatMap { v => if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs - }.toSeq.sortBy(_.defPCs.toList.min).foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => + }.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => val mappedWebs = reducedWebs.map(w => (w, w.identifiesSameVarAs(web))) if (!mappedWebs.exists(_._2)) { reducedWebs :+ web From dc41edb3b6335157990236938bf3d9a81e254f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 22 Jul 2024 20:40:10 +0200 Subject: [PATCH 502/583] Improve memory efficiency of joining environments --- .../string/StringTreeEnvironment.scala | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 387590867f..fa52ba2b1d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -13,12 +13,14 @@ import org.opalj.br.fpcf.properties.string.StringTreeOr */ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { - private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = map.keys.toList + private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = map.keys .filter(_.containsVarAt(pc, pv)) - .sortBy(_.defPCs.toList.min) + .toSeq + .sortBy(_.defPCs.toList.toSet.min) - private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys.toList + private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys .filter(_.identifiesSameVarAs(web)) + .toSeq .sortBy(_.defPCs.toList.min) private def getWebFor(pc: Int, pv: PV): PDUWeb = { @@ -64,16 +66,18 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { } } - def joinMany(others: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { + def joinMany(others: List[StringTreeEnvironment]): StringTreeEnvironment = { if (others.isEmpty) { this } else { - StringTreeEnvironment { - map.toList.concat(others.flatMap(_.map.toList)).groupBy(_._1).map { - case (k, vs) if vs.take(2).size == 1 => (k, vs.head._2) - case (k, vs) => (k, StringTreeOr.fromNodes(vs.map(_._2): _*)) - } - } + // This only works as long as environment maps are not sparse + StringTreeEnvironment(map.map { kv => + val otherValues = kv._2 +: others.map(_.map(kv._1)) + ( + kv._1, + StringTreeOr.fromNodes(otherValues: _*) + ) + }) } } } From 9b079c25aa261b60f25aaeb5ce93ea7c0dfcb0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 22 Jul 2024 20:44:24 +0200 Subject: [PATCH 503/583] Cache loop graphs during data flow analysis --- .../analyses/string/ComputationState.scala | 2 + .../string/MethodStringFlowAnalysis.scala | 7 +--- .../flowanalysis/DataFlowAnalysis.scala | 39 +++++++++++-------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 1c98fd8b82..b16389ef03 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -12,6 +12,7 @@ import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree +import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI @@ -35,6 +36,7 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: var flowGraph: FlowGraph = _ var superFlowGraph: SuperFlowGraph = _ var controlTree: ControlTree = _ + var flowAnalysis: DataFlowAnalysis = _ private val pcToDependeeMapping: mutable.Map[Int, EOptionP[MethodPC, StringFlowFunctionProperty]] = mutable.Map.empty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 43dba67ef9..d88da7561f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -69,6 +69,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entry) state.superFlowGraph = superFlowGraph state.controlTree = controlTree + state.flowAnalysis = new DataFlowAnalysis(state.controlTree, state.superFlowGraph) state.flowGraph.nodes.toOuter.foreach { case Statement(pc) if pc >= 0 => @@ -127,10 +128,6 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } }.toMap) - MethodStringFlow(DataFlowAnalysis.compute( - state.controlTree, - state.superFlowGraph, - state.getFlowFunctionsByPC - )(startEnv)) + MethodStringFlow(state.flowAnalysis.compute(state.getFlowFunctionsByPC)(startEnv)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 706a49ff5c..f3cdcf28cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -17,11 +17,14 @@ import scalax.collection.GraphTraversal.Parameters import scalax.collection.generic.Edge import scalax.collection.immutable.Graph -object DataFlowAnalysis { +class DataFlowAnalysis( + private val controlTree: ControlTree, + private val superFlowGraph: SuperFlowGraph +) { + + private val _removedBackEdgesGraphs = mutable.Map.empty[FlowGraphNode, (Boolean, SuperFlowGraph)] def compute( - controlTree: ControlTree, - superFlowGraph: SuperFlowGraph, flowFunctionByPc: Map[Int, StringFlowFunction] )(startEnv: StringTreeEnvironment): StringTreeEnvironment = { val startNodeCandidates = controlTree.nodes.filter(_.diPredecessors.isEmpty) @@ -30,22 +33,18 @@ object DataFlowAnalysis { } val startNode = startNodeCandidates.head.outer - pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc)(startNode, startEnv) + pipeThroughNode(flowFunctionByPc)(startNode, startEnv) } /** * @note This function should be stable with regards to an ordering on the piped flow graph nodes, e.g. a proper * region should always be traversed in the same way. */ - private def pipeThroughNode( - controlTree: ControlTree, - superFlowGraph: SuperFlowGraph, - flowFunctionByPc: Map[Int, StringFlowFunction] - )( + private def pipeThroughNode(flowFunctionByPc: Map[Int, StringFlowFunction])( node: FlowGraphNode, env: StringTreeEnvironment ): StringTreeEnvironment = { - val pipe = pipeThroughNode(controlTree, superFlowGraph, flowFunctionByPc) _ + val pipe = pipeThroughNode(flowFunctionByPc) _ val innerChildNodes = controlTree.get(node).diSuccessors.map(n => superFlowGraph.get(n.outer)) def processBlock(entry: FlowGraphNode): StringTreeEnvironment = { @@ -144,14 +143,20 @@ object DataFlowAnalysis { } def processNaturalLoop(entry: FlowGraphNode): StringTreeEnvironment = { - val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) - val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors - val removedBackEdgesGraph = limitedFlowGraph.filterNot( - edgeP = edge => - edge.sources.toList.toSet.intersect(entryPredecessors).nonEmpty - && edge.targets.contains(limitedFlowGraph.get(entry)) + val (isCyclic, removedBackEdgesGraph) = _removedBackEdgesGraphs.getOrElseUpdate( + node, { + val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) + val entryPredecessors = limitedFlowGraph.get(entry).diPredecessors + val computedRemovedBackEdgesGraph = limitedFlowGraph.filterNot( + edgeP = edge => + edge.sources.toList.toSet.intersect(entryPredecessors).nonEmpty + && edge.targets.contains(limitedFlowGraph.get(entry)) + ) + (computedRemovedBackEdgesGraph.isCyclic, computedRemovedBackEdgesGraph) + } ) - if (removedBackEdgesGraph.isCyclic) { + + if (isCyclic) { env.updateAll(StringTreeDynamicString) } else { // Handle resulting acyclic region From f7d932d7b53215144e966d8393bcd7b92461b6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sat, 27 Jul 2024 13:13:12 +0200 Subject: [PATCH 504/583] Introduce after phase scheduling to properties test --- .../string_analysis/l1/L1TestMethods.java | 10 +-- .../scala/org/opalj/fpcf/PropertiesTest.scala | 16 +--- .../org/opalj/fpcf/StringAnalysisTest.scala | 80 +++++++++---------- .../L0SystemPropertiesInterpreter.scala | 3 +- 4 files changed, 47 insertions(+), 62 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java index 3e9588b245..d380cfecd9 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java @@ -358,10 +358,7 @@ private void belongsToFieldReadTest() { @StringDefinitionsCollection( value = "a case where a field is read which is not written", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "(^null$|.*)" - ) + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(.*|^null$)") }) public void fieldWithNoWriteTest() { analyzeString(noWriteField); @@ -426,10 +423,7 @@ public void fieldInitByConstructorParameter() { @StringDefinitionsCollection( value = "a case where no callers information need to be computed", stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "(.*|value)" - ) + @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "value") }) public String cyclicDependencyTest(String s) { String value = getProperty(s); diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala index fef3b9b5d3..5b0ebc2ca7 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala @@ -361,7 +361,8 @@ abstract class PropertiesTest extends AnyFunSpec with Matchers { } def executeAnalyses( - analysisRunners: Iterable[ComputationSpecification[FPCFAnalysis]] + analysisRunners: Iterable[ComputationSpecification[FPCFAnalysis]], + afterPhaseScheduling: (Project[URL], List[ComputationSpecification[FPCFAnalysis]]) => Unit = (_, _) => () ): TestContext = { try { val p = FixtureProject.recreate { piKeyUnidueId => piKeyUnidueId != PropertyStoreKey.uniqueId } // to ensure that this project is not "polluted" @@ -372,21 +373,12 @@ abstract class PropertiesTest extends AnyFunSpec with Matchers { p.getOrCreateProjectInformationKeyInitializationData( PropertyStoreKey, - (context: List[PropertyStoreContext[AnyRef]]) => { - /* - val ps = PKEParallelTasksPropertyStore.create( - new RecordAllPropertyStoreTracer, - context.iterator.map(_.asTuple).toMap - ) - */ - val ps = PKESequentialPropertyStore(context: _*) - ps - } + (context: List[PropertyStoreContext[AnyRef]]) => PKESequentialPropertyStore(context: _*) ) val ps = p.get(PropertyStoreKey) - val (_, csas) = p.get(FPCFAnalysesManagerKey).runAll(analysisRunners) + val (_, csas) = p.get(FPCFAnalysesManagerKey).runAll(analysisRunners, afterPhaseScheduling(p, _)) TestContext(p, ps, csas.collect { case (_, as) => as }) } catch { case t: Throwable => 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 c295ccf3ea..65e44deb36 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 @@ -18,6 +18,8 @@ import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.properties.string_analysis.DomainLevel import org.opalj.fpcf.properties.string_analysis.SoundnessMode @@ -41,6 +43,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { val nameTestMethod: String = "analyzeString" def level: Int + def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] def domainLevel: DomainLevel def soundnessMode: SoundnessMode @@ -71,6 +74,27 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { def initBeforeCallGraph(p: Project[URL]): Unit = {} + def runTests(): Unit = { + describe(s"the string analysis on level $level is started") { + var entities = Iterable.empty[(VariableContext, Method)] + val as = executeAnalyses( + analyses, + (project, currentPhaseAnalyses) => { + if (currentPhaseAnalyses.exists(_.derives.exists(_.pk == StringConstancyProperty))) { + val ps = project.get(PropertyStoreKey) + entities = determineEntitiesToAnalyze(project) + entities.foreach(entity => ps.force(entity._1, StringConstancyProperty.key)) + } + } + ) + + as.propertyStore.waitOnPhaseCompletion() + as.propertyStore.shutdown() + + validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + } + } + override def fixtureProjectPackage: List[String] = { StringAnalysisTest.getFixtureProjectPackages(level).toList } @@ -239,21 +263,9 @@ sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { override final def level = 0 - final def runL0Tests(): Unit = { - describe("the org.opalj.fpcf.L0StringAnalysis is started") { - val as = executeAnalyses( - LazyL0StringAnalysis.allRequiredAnalyses :+ - EagerSystemPropertiesAnalysisScheduler - ) - - val entities = determineEntitiesToAnalyze(as.project) - entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) - - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() - - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) - } + override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { + LazyL0StringAnalysis.allRequiredAnalyses :+ + EagerSystemPropertiesAnalysisScheduler } } @@ -262,7 +274,7 @@ class L0StringAnalysisWithL1DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - describe("using the l1 default domain") { runL0Tests() } + describe("using the l1 default domain") { runTests() } } class L0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { @@ -270,7 +282,7 @@ class L0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - describe("using the l2 default domain") { runL0Tests() } + describe("using the l2 default domain") { runTests() } } class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { @@ -278,7 +290,7 @@ class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnaly override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH - describe("using the l2 default domain and the high soundness mode") { runL0Tests() } + describe("using the l2 default domain and the high soundness mode") { runTests() } } /** @@ -291,32 +303,18 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { override def level = 1 + override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { + LazyL1StringAnalysis.allRequiredAnalyses :+ + EagerFieldAccessInformationAnalysis :+ + EagerSystemPropertiesAnalysisScheduler + } + override def initBeforeCallGraph(p: Project[URL]): Unit = { p.updateProjectInformationKeyInitializationData(FieldAccessInformationKey) { case None => Seq(EagerFieldAccessInformationAnalysis) case Some(requirements) => requirements :+ EagerFieldAccessInformationAnalysis } } - - final def runL1Tests(): Unit = { - describe("the org.opalj.fpcf.L1StringAnalysis is started") { - val as = executeAnalyses( - LazyL1StringAnalysis.allRequiredAnalyses :+ - EagerFieldAccessInformationAnalysis :+ - EagerSystemPropertiesAnalysisScheduler - ) - - val entities = determineEntitiesToAnalyze(as.project) - // Currently broken L1 Tests - .filterNot(entity => entity._2.name.startsWith("cyclicDependencyTest")) - entities.foreach(entity => as.propertyStore.force(entity._1, StringConstancyProperty.key)) - - as.propertyStore.waitOnPhaseCompletion() - as.propertyStore.shutdown() - - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) - } - } } class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { @@ -324,7 +322,7 @@ class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - describe("using the l1 default domain") { runL1Tests() } + describe("using the l1 default domain") { runTests() } } class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { @@ -332,7 +330,7 @@ class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - describe("using the l2 default domain") { runL1Tests() } + describe("using the l2 default domain") { runTests() } } class HighSoundnessL1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { @@ -340,5 +338,5 @@ class HighSoundnessL1StringAnalysisWithL2DefaultDomainTest extends L1StringAnaly override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH - describe("using the l2 default domain and the high soundness mode") { runL1Tests() } + describe("using the l2 default domain and the high soundness mode") { runTests() } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala index 1f85a6e18f..2a2bcc1167 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala @@ -43,8 +43,9 @@ private[string] trait L0SystemPropertiesInterpreter extends StringInterpreter { Set(depender.dependee), continuation(state, depender) ) + } else { + continuation(state, depender)(depender.dependee.asInstanceOf[SomeEPS]) } - continuation(state, depender)(depender.dependee.asInstanceOf[SomeEPS]) } private def continuation( From 16cba1583f443c52f844f9c3f21ab8803e3237a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 29 Jul 2024 21:51:26 +0200 Subject: [PATCH 505/583] Rework string analysis tests --- .../fixtures/string_analysis/ArrayOps.java | 56 + .../fixtures/string_analysis/Complex.java | 117 ++ .../ExceptionalControlStructures.java | 69 + .../fixtures/string_analysis/External.java | 144 ++ .../string_analysis/FunctionCalls.java | 227 +++ .../fixtures/string_analysis/Integration.java | 43 + .../fpcf/fixtures/string_analysis/Loops.java | 249 +++ .../SimpleControlStructures.java | 120 ++ .../SimpleStringBuilderOps.java | 165 ++ .../string_analysis/SimpleStringOps.java | 294 ++++ .../string_analysis/l0/L0TestMethods.java | 1348 ----------------- .../string_analysis/l1/L1TestMethods.java | 681 --------- .../GreetingService.java | 2 +- .../hierarchies => tools}/HelloGreeting.java | 2 +- .../SimpleHelloGreeting.java | 2 +- .../{l0 => tools}/StringProvider.java | 9 +- .../properties/string_analysis/Constant.java | 29 + ...llowedDomainLevels.java => Constants.java} | 7 +- .../properties/string_analysis/Dynamic.java | 29 + ...lowedSoundnessModes.java => Dynamics.java} | 7 +- .../properties/string_analysis/Failure.java | 21 + .../properties/string_analysis/Failures.java | 12 + .../properties/string_analysis/Invalid.java | 24 + .../properties/string_analysis/Invalids.java | 12 + .../properties/string_analysis/Level.java | 22 + .../string_analysis/PartiallyConstant.java | 29 + .../string_analysis/PartiallyConstants.java | 12 + .../string_analysis/StringConstancyLevel.java | 30 - .../string_analysis/StringDefinitions.java | 46 - .../StringDefinitionsCollection.java | 27 - .../org/opalj/fpcf/StringAnalysisTest.scala | 202 ++- .../StringAnalysisMatcher.scala | 90 -- .../string_analysis/StringMatcher.scala | 75 + 33 files changed, 1852 insertions(+), 2350 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{l1/hierarchies => tools}/GreetingService.java (68%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{l1/hierarchies => tools}/HelloGreeting.java (77%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{l1/hierarchies => tools}/SimpleHelloGreeting.java (77%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/{l0 => tools}/StringProvider.java (71%) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/{AllowedDomainLevels.java => Constants.java} (68%) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/{AllowedSoundnessModes.java => Dynamics.java} (67%) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala create mode 100644 DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java new file mode 100644 index 0000000000..27c2825e5b --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.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.fixtures.string_analysis.tools.StringProvider; +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * @see SimpleStringOps + */ +public class ArrayOps { + + private String[] monthNames = { "January", "February", "March", getApril() }; + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Constant(n = 0, value = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", levels = Level.TRUTH) + @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "array accesses cannot be interpreted yet") + 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) { + analyzeString(classes[index]); + } + } + + @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)") + @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "arrays are not supported") + public void arrayStaticAndVirtualFunctionCalls(int i) { + String[] classes = { + "java.lang.Object", + getRuntimeClassName(), + StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), + System.getProperty("SomeClass") + }; + analyzeString(classes[i]); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(January|February|March|April)") + @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "arrays are not supported") + public void getStringArrayField(int i) { + analyzeString(monthNames[i]); + } + + private String getRuntimeClassName() { + return "java.lang.Runtime"; + } + + private String getApril() { + return "April"; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java new file mode 100644 index 0000000000..d8df07c765 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java @@ -0,0 +1,117 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import java.lang.reflect.Method; +import java.util.Random; + +/** + * @see SimpleStringOps + */ +public class Complex { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + /** + * Extracted from com.oracle.webservices.internal.api.message.BasePropertySet, has two def-sites and one use-site + */ + @PartiallyConstant(n = 0, value = "(set.*|s.*)") + 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"); + } + } + + /** + * Taken from com.sun.javafx.property.PropertyReference#reflect. + */ + @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, World.*)") + @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, Worldjava.lang.Runtime)") + public void complexDependencyResolve(String s, Class clazz) { + String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : + getHelloWorld() + getRuntimeClassName(); + String getterName = "get" + properName; + Method m; + try { + m = clazz.getMethod(getterName); + System.out.println(m); + analyzeString(getterName); + } catch (NoSuchMethodException var13) { + } + } + + /** + * Taken from com.sun.prism.impl.ps.BaseShaderContext#getPaintShader and slightly adapted + */ + @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") + // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking + @Constant(n = 0, levels = { Level.L0, Level.L1 }, value = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_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); + } + + @Failure(n = 0, levels = Level.TRUTH) + @Failure(n = 1, levels = Level.TRUTH) + 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()); + } + + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.L1, value = "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"; + } + + private static String getHelloWorld() { + return "Hello, World"; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java new file mode 100644 index 0000000000..3dbe548f90 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java @@ -0,0 +1,69 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @see SimpleStringOps + */ +public class ExceptionalControlStructures { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + // Multiple calls to "analyzeString" are generated by the Java compiler, hence we have multiple definitions + @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "File Content:.*") + @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + public void tryFinally(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()); + } + } + + @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "=====.*") + // Exception case without own thrown exception + @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====)") + // The following cases are detected: + // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 3) + // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 1) + // 3. First append succeeds, throws no exception -> only first append (Pos 4) + // 4. First append is executed but throws an exception Throwable -> both appends (Pos 2) + @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====|=====|=====.*)") + 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()); + } + } + + @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "BOS:.*") + @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") + @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*: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()); + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java new file mode 100644 index 0000000000..d56ca405b8 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -0,0 +1,144 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import javax.management.remote.rmi.RMIServer; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + +/** + * @see SimpleStringOps + */ +public class External { + + protected String nonFinalNonStaticField = "private l0 non-final string field"; + public static String nonFinalStaticField = "will not be revealed here"; + public static final String finalStaticField = "mine"; + private String fieldWithSelfInit = "init field value"; + private static final String fieldWithSelfInitWithOutOfScopeCall = RMIServer.class.getName() + "Impl_Stub"; + private String fieldWithConstructorInit; + private float fieldWithConstructorParameterInit; + private String writeInSameMethodField; + private String noWriteField; + private Object unsupportedTypeField; + + public External(float e) { + fieldWithConstructorInit = "initialized by constructor"; + fieldWithConstructorParameterInit = e; + } + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Constant(n = 0, levels = Level.TRUTH, value = "Field Value:private l0 non-final string field") + @Invalid(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW) + @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "Field Value:.*") + public void nonFinalFieldRead() { + StringBuilder sb = new StringBuilder("Field Value:"); + System.out.println(sb); + sb.append(nonFinalNonStaticField); + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "will not be revealed here") + @Failure(n = 0, levels = Level.L0) + public void nonFinalStaticFieldRead() { + analyzeString(nonFinalStaticField); + } + + @Constant(n = 0, value = "Field Value:mine") + public void publicFinalStaticFieldRead() { + StringBuilder sb = new StringBuilder("Field Value:"); + System.out.println(sb); + sb.append(finalStaticField); + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "init field value") + @Failure(n = 0, levels = Level.L0) + public void fieldWithInitRead() { + analyzeString(fieldWithSelfInit.toString()); + } + + @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = ".*Impl_Stub") + @Failure(n = 0, levels = Level.L0) + public void fieldWithInitWithOutOfScopeRead() { + analyzeString(fieldWithSelfInitWithOutOfScopeCall); + } + + @Constant(n = 0, value = "initialized by constructor") + @Failure(n = 0, levels = Level.L0) + public void fieldInitByConstructorRead() { + analyzeString(fieldWithConstructorInit.toString()); + } + + @Dynamic(n = 0, levels = Level.TRUTH, value = "^-?\\d*\\.{0,1}\\d+$") + @Failure(n = 0, levels = Level.L0, domains = DomainLevel.L1) + @Invalid(n = 0, levels = Level.L0, domains = DomainLevel.L2, soundness = SoundnessMode.LOW) + @Dynamic(n = 0, levels = Level.L0, domains = DomainLevel.L2, soundness = SoundnessMode.HIGH, + value = "^-?\\d*\\.{0,1}\\d+$", reason = "the field value is inlined using L2 domains") + public void fieldInitByConstructorParameter() { + analyzeString(new StringBuilder().append(fieldWithConstructorParameterInit).toString()); + } + + // Contains a field write in the same method which cannot be captured by flow functions + @Constant(n = 0, levels = Level.TRUTH, value = "(some value|^null$)") + @Failure(n = 0, levels = Level.L0) + @Dynamic(n = 0, levels = Level.L1, value = ".*") + public void fieldWriteInSameMethod() { + writeInSameMethodField = "some value"; + analyzeString(writeInSameMethodField); + } + + @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*|^null$)") + @Failure(n = 0, levels = Level.L0) + public void fieldWithNoWriteTest() { + analyzeString(noWriteField); + } + + @Failure(n = 0, levels = { Level.L0, Level.L1 }) + public void nonSupportedFieldTypeRead() { + analyzeString(unsupportedTypeField.toString()); + } + + @Dynamic(n = 0, value = ".*") + @Dynamic(n = 1, value = ".*") + @PartiallyConstant(n = 2, value = "value=.*") + @PartiallyConstant(n = 3, value = "value=.*.*") + 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()); + } + + /** + * Methods are called that return a string but are not within this project => cannot / will not interpret + */ + @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") + @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, value = ".*") + @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) + @Dynamic(n = 1, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.LOW, value = ".*") + @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") + public void methodsOutOfScopeTest() 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()); + + analyzeString(System.clearProperty("os.version")); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java new file mode 100644 index 0000000000..ec2ec38b16 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -0,0 +1,227 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.fixtures.string_analysis.tools.StringProvider; +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * @see SimpleStringOps + */ +public class FunctionCalls { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + // checks if a string value with append(s) is determined correctly + @Constant(n = 0, value = "java.lang.String") + @Constant(n = 1, value = "java.lang.Object") + public void simpleStringConcatWithStaticFunctionCalls() { + analyzeString(StringProvider.concat("java.lang.", "String")); + analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.StringBuilder") + @Failure(n = 0, levels = Level.L0) + public void fromFunctionCall() { + analyzeString(getStringBuilderClassName()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.StringBuilder") + @Invalid(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW) + @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "java.lang..*") + public void fromConstantAndFunctionCall() { + String className = "java.lang."; + System.out.println(className); + className += getSimpleStringBuilderClassName(); + analyzeString(className); + } + + @Constant(n = 0, value = "java.lang.Integer") + public void fromStaticMethodWithParamTest() { + analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); + } + + @Invalid(n = 0, levels = Level.TRUTH, reason = "the function has no return value, thus it does not return a string") + public void functionWithNoReturnValue() { + analyzeString(noReturnFunction()); + } + + /** Belongs to functionWithNoReturnValue. */ + public static String noReturnFunction() { + throw new RuntimeException(); + } + + @Constant(n = 0, value = "Hello, World!") + @Constant(n = 1, levels = Level.TRUTH, value = "Hello, World?") + @Failure(n = 1, levels = Level.L0) + public void functionWithFunctionParameter() { + analyzeString(addExclamationMark(getHelloWorld())); + analyzeString(addQuestionMark(getHelloWorld())); + } + + @Constant(n = 0, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") + @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "ERROR") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") + public void simpleNonVirtualFunctionCallTestWithIf(int i) { + String s; + if (i == 0) { + s = getObjectClassName(); + } else if (i == 1) { + s = getStringBuilderClassName(); + } else { + s = "ERROR"; + } + analyzeString(s); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") + @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "ERROR") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") + public void initFromNonVirtualFunctionCallTest(int i) { + String s; + if (i == 0) { + s = getObjectClassName(); + } else if (i == 1) { + s = getStringBuilderClassName(); + } else { + s = "ERROR"; + } + StringBuilder sb = new StringBuilder(s); + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "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()); + } + + /** + * A case where the single valid return value of the called function can be resolved without calling the function. + */ + @Constant(n = 0, levels = Level.TRUTH, domains = DomainLevel.L1, value = "(java.lang.Object|One|val)") + @Failure(n = 0, levels = Level.L0, domains = DomainLevel.L1) + // Since the virtual function return value is inlined in L2 and its actual runtime return + // value is not used, the function call gets converted to a method call, which modifies the + // TAC: The def PC from the `analyzeString` parameter is now different and points to the def + // PC for the `resolvableReturnValueFunction` parameter. This results in no string flow being + // detected since the def and use sites are now inconsistent. + // The actual truth @Constant(n = 0, value = "val", domains = DomainLevel.L2) + @Invalid(n = 0, levels = Level.L0, domains = DomainLevel.L2) + @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2) + public void resolvableReturnValue() { + analyzeString(resolvableReturnValueFunction("val", 42)); + } + + /** + * Belongs to resolvableReturnValue. + */ + private String resolvableReturnValueFunction(String s, int i) { + switch (i) { + case 0: return getObjectClassName(); + case 1: return "One"; + default: return s; + } + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(One|val|java.lang.Object)") + @Failure(n = 0, levels = Level.L0) + public void severalReturnValuesTest1() { + analyzeString(severalReturnValuesWithSwitchFunction("val", 42)); + } + + /** Belongs to severalReturnValuesTest1. */ + private String severalReturnValuesWithSwitchFunction(String s, int i) { + switch (i) { + case 0: return "One"; + case 1: return s; + default: return getObjectClassName(); + } + } + + @Constant(n = 0, value = "(that's odd|Hello, World)") + public void severalReturnValuesTest2() { + analyzeString(severalReturnValuesWithIfElseFunction(42)); + } + + /** Belongs to severalReturnValuesTest2. */ + private static String severalReturnValuesWithIfElseFunction(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 getHelloWorld(); + } + } + + @Constant(n = 0, value = "(Hello, World|my.helper.Class)") + public String calleeWithFunctionParameter(String s, float i) { + analyzeString(s); + return s; + } + + @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World") + @Failure(n = 0, levels = Level.L0) + public void firstCallerForCalleeWithFunctionParameter() { + String s = calleeWithFunctionParameter(getHelloWorldProxy(), 900); + analyzeString(s); + } + + public void secondCallerForCalleeWithFunctionParameter() { + calleeWithFunctionParameter(getHelperClassProxy(), 900); + } + + @Constant(n = 0, value = "(Hello, World|my.helper.Class)") + public String calleeWithFunctionParameterMultipleCallsInSameMethodTest(String s, float i) { + analyzeString(s); + return s; + } + + public void callerForCalleeWithFunctionParameterMultipleCallsInSameMethodTest() { + calleeWithFunctionParameterMultipleCallsInSameMethodTest(getHelloWorldProxy(), 900); + calleeWithFunctionParameterMultipleCallsInSameMethodTest(getHelperClassProxy(), 900); + } + + public static String getHelloWorldProxy() { + return getHelloWorld(); + } + + public static String getHelperClassProxy() { + return getHelperClass(); + } + + private static String getHelloWorld() { + return "Hello, World"; + } + + private static String getHelperClass() { + return "my.helper.Class"; + } + + private String getStringBuilderClassName() { + return "java.lang.StringBuilder"; + } + + private String getSimpleStringBuilderClassName() { + return "StringBuilder"; + } + + private String getObjectClassName() { + return "java.lang.Object"; + } + + private static String addExclamationMark(String s) { + return s + "!"; + } + + private String addQuestionMark(String s) { + return s + "?"; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java new file mode 100644 index 0000000000..f5cd63fbab --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.fixtures.string_analysis.tools.GreetingService; +import org.opalj.fpcf.fixtures.string_analysis.tools.HelloGreeting; +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * @see SimpleStringOps + */ +public class Integration { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Constant(n = 0, value = "java.lang.String") + public void noCallersInformationRequiredTest(String s) { + System.out.println(s); + analyzeString("java.lang.String"); + } + + @Constant(n = 0, value = "some.test.value") + public void systemPropertiesIntegrationTest() { + System.setProperty("some.test.property", "some.test.value"); + String s = System.getProperty("some.test.property"); + analyzeString(s); + } + + @Constant(n = 0, value = "Hello World") + @Failure(n = 0, levels = Level.L0) + public void knownHierarchyInstanceTest() { + GreetingService gs = new HelloGreeting(); + analyzeString(gs.getGreeting("World")); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello World|Hello)") + @Failure(n = 0, levels = Level.L0) + public void unknownHierarchyInstanceTest(GreetingService greetingService) { + analyzeString(greetingService.getGreeting("World")); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java new file mode 100644 index 0000000000..430a7a7fc4 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -0,0 +1,249 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Random; + +/** + * @see SimpleStringOps + */ +public class Loops { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + /** + * Simple for loops with known and unknown bounds. Note that no analysis supports loops yet. + */ + @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + @PartiallyConstant(n = 1, value = "a(b)*", levels = Level.TRUTH) + @Dynamic(n = 1, value = ".*", levels = { Level.L0, Level.L1 }) + public void simpleForLoopWithKnownBounds() { + StringBuilder sb = new StringBuilder("a"); + for (int i = 0; i < 10; i++) { + sb.append("b"); + } + analyzeString(sb.toString()); + + int limit = new Random().nextInt(); + sb = new StringBuilder("a"); + for (int i = 0; i < limit; i++) { + sb.append("b"); + } + analyzeString(sb.toString()); + } + + @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) + @Dynamic(n = 0, value = "(.*|.*yz)", levels = { Level.L0, Level.L1 }) + 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()); + } + + @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + 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()); + } + } + + @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) + @Dynamic(n = 0, value = "(.*|.*yz)", levels = { Level.L0, Level.L1 }) + 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()); + } + + @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + public void whileTrueWithBreak() { + StringBuilder sb = new StringBuilder("a"); + while (true) { + sb.append("b"); + if (sb.length() > 100) { + break; + } + } + analyzeString(sb.toString()); + } + + @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + public void whileNonTrueWithBreak(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()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(iv1|iv2): ") + // The real value is not fully resolved yet, since the string builder is used in a while loop, + // which leads to the string builder potentially carrying any value. This can be refined by + // recording pc specific states during data flow analysis. + @Dynamic(n = 0, levels = { Level.L0 , Level.L1 }, soundness = SoundnessMode.LOW, value = "((iv1|iv2): |.*)") + @Dynamic(n = 0, levels = { Level.L0 , Level.L1 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") + @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?") + @Dynamic(n = 1, levels = Level.L0, soundness = SoundnessMode.LOW, value = ".*") + @Dynamic(n = 1, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|.*.*)") + @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(.*|.*java.lang.Runtime)") + @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") + public void extensiveWithManyControlStructures(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()); + } + + analyzeString(sb.toString()); + } + + // The bytecode produces an "if" within an "if" inside the first loop => two conditions + @Constant(n = 0, levels = Level.TRUTH, value = "abc((d)?)*") + @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, value = ".*") + @Constant(n = 1, levels = Level.TRUTH, value = "") + @Dynamic(n = 1, levels = { Level.L0, Level.L1 }, value = "(.*|)") + @Dynamic(n = 2, levels = Level.TRUTH, value = "((.*)?)*") + @Dynamic(n = 2, levels = { Level.L0, Level.L1 }, value = ".*") + 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()); + } + + /** + * Some comprehensive example for experimental purposes taken from the JDK and slightly modified + */ + @Constant(n = 0, value = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?", levels = Level.TRUTH) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + 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(); + if (i % 2 == 0) { + System.out.println("Ready to analyze now in any case!" + i); + } + } + + analyzeString(sb.toString()); + } + } + + 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/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java new file mode 100644 index 0000000000..88fd27ed7c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java @@ -0,0 +1,120 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import java.util.Random; + +/** + * @see SimpleStringOps + */ +public class SimpleControlStructures { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Dynamic(n = 0, value = "(x|^-?\\d+$)") + @Constant(n = 1, value = "(42-42|x)") + public void ifElseWithStringBuilderWithIntExpr() { + StringBuilder sb1 = new StringBuilder(); + StringBuilder sb2 = new StringBuilder(); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb1.append("x"); + sb2.append(42); + sb2.append(-42); + } else { + sb1.append(i + 1); + sb2.append("x"); + } + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); + } + + @PartiallyConstant(n = 0, value = "(3.142.71828|^-?\\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()); + } + + @Constant(n = 0, value = "(a|b)") + @Constant(n = 1, value = "(ab|ac)") + public void ifElseWithStringBuilder() { + StringBuilder sb1; + StringBuilder sb2 = new StringBuilder("a"); + + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb1 = new StringBuilder("a"); + sb2.append("b"); + } else { + sb1 = new StringBuilder("b"); + sb2.append("c"); + } + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); + } + + @Constant(n = 0, value = "(abcd|axyz)") + public void ifElseWithStringBuilderWithMultipleAppends() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("b"); + sb.append("c"); + sb.append("d"); + } else { + sb.append("x"); + sb.append("y"); + sb.append("z"); + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(abcd|a|axyz)") + public void ifElseWithStringBuilderWithMultipleAppendsAndNonUsedElseIf() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 3 == 0) { + sb.append("b"); + sb.append("c"); + sb.append("d"); + } else if (i % 2 == 0) { + System.out.println("something"); + } else { + sb.append("x"); + sb.append("y"); + sb.append("z"); + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(a|ab)") + public void ifWithoutElse() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("b"); + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "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()); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java new file mode 100644 index 0000000000..c955e7e503 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java @@ -0,0 +1,165 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @see SimpleStringOps + */ +public class SimpleStringBuilderOps { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + public void analyzeString(StringBuilder sb) {} + + @Constant(n = 0, value = "java.lang.String") + public void multipleDirectAppends() { + StringBuilder sb = new StringBuilder("java"); + sb.append(".").append("lang").append(".").append("String"); + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "SomeOther") + @Constant(n = 1, value = "SomeOther", levels = Level.TRUTH) + @Constant(n = 1, value = "(Some|SomeOther)", levels = { Level.L0, Level.L1 }) + public void stringValueOfWithStringBuilder() { + StringBuilder sb = new StringBuilder("Some"); + sb.append("Other"); + analyzeString(String.valueOf(sb)); + + analyzeString(sb); + } + + @Constant(n = 0, value = "Some") + @Constant(n = 1, value = "Other") + public void stringBuilderBufferInitArguments() { + StringBuilder sb = new StringBuilder("Some"); + analyzeString(sb.toString()); + + StringBuffer sb2 = new StringBuffer("Other"); + analyzeString(sb2.toString()); + } + + @Constant(n = 0, value = "java.lang.StringBuilder") + @Constant(n = 1, value = "java.lang.StringBuilder") + public void simpleClearExamples() { + StringBuilder sb1 = new StringBuilder("init_value:"); + sb1.setLength(0); + sb1.append("java.lang.StringBuilder"); + + StringBuilder sb2 = new StringBuilder("init_value:"); + System.out.println(sb2.toString()); + sb2 = new StringBuilder(); + sb2.append("java.lang.StringBuilder"); + + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); + } + + @Constant(n = 0, value = "(Goodbye|init_value:Hello, world!Goodbye)") + public void advancedClearExampleWithSetLength(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()); + } + + @Dynamic(n = 0, value = ".*") + @PartiallyConstant(n = 1, value = "(.*Goodbye|init_value:Hello, world!Goodbye)") + public void replaceExamples(int value) { + StringBuilder sb1 = new StringBuilder("init_value"); + sb1.replace(0, 5, "replaced_value"); + analyzeString(sb1.toString()); + + sb1 = new StringBuilder("init_value:"); + if (value < 10) { + sb1.replace(0, value, "..."); + } else { + sb1.append("Hello, world!"); + } + sb1.append("Goodbye"); + analyzeString(sb1.toString()); + } + + @Constant(n = 0, value = "B.") + @Constant(n = 1, value = "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(sb2.toString()); + analyzeString(sb.toString()); + } + + /** + * Checks if the value of a string builder that depends on the complex construction of a second one can be determined. + */ + @Constant(n = 0, value = "java.lang.(Object|Runtime)") + public void complexSecondStringBuilderRead(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()); + } + + @Constant(n = 0, value = "(java.lang.Object|java.lang.Runtime)") + public void simpleSecondStringBuilderRead(String className) { + StringBuilder sbObj = new StringBuilder("Object"); + StringBuilder sbRun = new StringBuilder("Runtime"); + + StringBuilder sb1 = new StringBuilder("java.lang."); + if (sb1.length() == 0) { + sb1.append(sbObj.toString()); + } else { + sb1.append(sbRun.toString()); + } + + analyzeString(sb1.toString()); + } + + @Constant(n = 0, value = "(Object|ObjectRuntime)") + @Constant(n = 1, value = "(RuntimeObject|Runtime)") + 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()); + } + + @PartiallyConstant(n = 0, value = "File Content:.*", soundness = SoundnessMode.HIGH) + public void withUnknownAppendSource(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()); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java new file mode 100644 index 0000000000..802b2a0ffd --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java @@ -0,0 +1,294 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * All files in this package define various tests for the string analysis. 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.
    • + *
    • + * 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": + * {*, ?, \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 Maximilian Rüsch + */ +public class SimpleStringOps { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + // read-only string variable, trivial case + @Constant(n = 0, value = "java.lang.String") + @Constant(n = 1, value = "java.lang.String") + public void constantStringReads() { + analyzeString("java.lang.String"); + + String className = "java.lang.String"; + analyzeString(className); + } + + @Constant(n = 0, value = "c") + @Constant(n = 1, value = "42.3") + @Constant(n = 2, levels = Level.TRUTH, value = "java.lang.Runtime") + @Failure(n = 2, levels = Level.L0) + public void valueOfTest() { + analyzeString(String.valueOf('c')); + analyzeString(String.valueOf((float) 42.3)); + analyzeString(String.valueOf(getRuntimeClassName())); + } + + // checks if a string value with append(s) is determined correctly + @Constant(n = 0, value = "java.lang.String") + @Constant(n = 1, value = "java.lang.Object") + public void simpleStringConcat() { + String className1 = "java.lang."; + System.out.println(className1); + className1 += "String"; + analyzeString(className1); + + String className2 = "java."; + System.out.println(className2); + className2 += "lang."; + System.out.println(className2); + className2 += "Object"; + analyzeString(className2); + } + + // checks if the substring of a constant string value is determined correctly + @Constant(n = 0, value = "va.") + @Constant(n = 1, value = "va.lang.") + public void simpleSubstring() { + String someString = "java.lang."; + analyzeString(someString.substring(2, 5)); + analyzeString(someString.substring(2)); + } + + @Constant(n = 0, value = "(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); + } + + @Constant(n = 0, value = "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()); + } + + @Constant(n = 0, value = "(Some|SomeOther)") + @Dynamic(n = 1, value = "(.*|Some)") + @PartiallyConstant(n = 2, value = "(SomeOther|Some.*)") + public void ternaryOperators(boolean flag, String param) { + String s1 = "Some"; + String s2 = s1 + "Other"; + + analyzeString(flag ? s1 : s2); + analyzeString(flag ? s1 : param); + analyzeString(flag ? s1 + param : s2); + } + + @Constant(n = 0, value = "(a|ab|ac)") + public void switchRelevantAndIrrelevant(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()); + } + + @Constant(n = 0, value = "(ab|ac|a|ad)") + public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(a|ab|ac)") + public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(ab|ac|ad)") + public void switchRelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(ab|ac|a|ad|af)") + public void switchNestedNoNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + } + break; + default: + sb.append("f"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, value = "(ab|ac|ad|ae|af)") + public void switchNestedWithNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + default: + sb.append("e"); + break; + } + break; + default: + sb.append("f"); + break; + } + analyzeString(sb.toString()); + } + + /** + * A more comprehensive case where multiple definition sites have to be considered each with a different string + * generation mechanism + */ + @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.System|java.lang.StringBuilder)") + @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "java.lang.System") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang..*)") + @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") + @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") + 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"; + } + + private String getSimpleStringBuilderClassName() { + return "StringBuilder"; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java deleted file mode 100644 index 2ed6637b44..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/L0TestMethods.java +++ /dev/null @@ -1,1348 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l0; - -import org.opalj.fpcf.properties.string_analysis.AllowedSoundnessModes; -import org.opalj.fpcf.properties.string_analysis.SoundnessMode; -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; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Random; - -import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; - -/** - * This file contains various tests for the L0StringAnalysis. 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. - *
    • - *
    • - * 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": - * {*, ?, \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 Maximilian Rüsch - */ -public class L0TestMethods { - - protected String someStringField = "private l0 non-final string field"; - 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.L0StringAnalysisTest} 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) { - } - - public void analyzeString(StringBuilder sb) { - } - - @StringDefinitionsCollection( - value = "read-only string variable, trivial case", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") - } - ) - public void constantStringReads() { - analyzeString("java.lang.String"); - - String className = "java.lang.String"; - analyzeString(className); - } - - @StringDefinitionsCollection( - value = "checks if a string value with append(s) is determined correctly", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Object") - } - ) - public void simpleStringConcat() { - String className1 = "java.lang."; - System.out.println(className1); - className1 += "String"; - analyzeString(className1); - - String className2 = "java."; - System.out.println(className2); - className2 += "lang."; - System.out.println(className2); - className2 += "Object"; - analyzeString(className2); - } - - @StringDefinitionsCollection( - value = "checks if a string value with append(s) is determined correctly", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String") - } - ) - public void simpleStringConcat2() { - String className1 = "java.lang."; - System.out.println(className1); - className1 += "String"; - analyzeString(className1); - } - - @StringDefinitionsCollection( - value = "checks if the substring of a constant string value is determined correctly", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "va."), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "va.lang.") - } - ) - public void simpleSubstring() { - String someString = "java.lang."; - analyzeString(someString.substring(2, 5)); - analyzeString(someString.substring(2)); - } - - @StringDefinitionsCollection( - value = "checks if a string value with append(s) is determined correctly", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.String"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Object") - } - ) - public void simpleStringConcatWithStaticFunctionCalls() { - analyzeString(StringProvider.concat("java.lang.", "String")); - analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); - } - - @StringDefinitionsCollection( - value = "checks if a string value with > 2 continuous appends is determined correctly", - 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()); - } - - @StringDefinitionsCollection( - value = "tests support for passing a string builder into String.valueOf and directly as the analyzed string", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "SomeOther"), - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "SomeOther", - realisticLevel = CONSTANT, - realisticStrings = "(Some|SomeOther)" - ) - }) - public void stringValueOfWithStringBuilder() { - StringBuilder sb = new StringBuilder("Some"); - sb.append("Other"); - analyzeString(String.valueOf(sb)); - - analyzeString(sb); - } - - @StringDefinitionsCollection( - value = "tests support for passing a string builder into String.valueOf and directly as the analyzed string", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Some"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Other") - }) - public void stringBuilderBufferInitArguments() { - StringBuilder sb = new StringBuilder("Some"); - analyzeString(sb.toString()); - - StringBuffer sb2 = new StringBuffer("Other"); - analyzeString(sb2.toString()); - } - - @AllowedSoundnessModes(SoundnessMode.LOW) - @StringDefinitionsCollection( - value = "at this point, function call cannot be handled => DYNAMIC", - stringDefinitions = { - @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) - }) - public void fromFunctionCallLowSoundness() { - String className = getStringBuilderClassName(); - analyzeString(className); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "at this point, function call cannot be handled => DYNAMIC", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") - }) - public void fromFunctionCall() { - String className = getStringBuilderClassName(); - analyzeString(className); - } - - @AllowedSoundnessModes(SoundnessMode.LOW) - @StringDefinitionsCollection( - value = "constant string + string from function call => PARTIALLY_CONSTANT", - stringDefinitions = { - @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) - }) - public void fromConstantAndFunctionCallLowSoundness() { - String className = "java.lang."; - System.out.println(className); - className += getSimpleStringBuilderClassName(); - analyzeString(className); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "constant string + string from function call => PARTIALLY_CONSTANT", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang..*") - }) - public void fromConstantAndFunctionCall() { - String className = "java.lang."; - System.out.println(className); - className += getSimpleStringBuilderClassName(); - analyzeString(className); - } - - @AllowedSoundnessModes(SoundnessMode.LOW) - @StringDefinitionsCollection( - value = "array access with unknown index", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", - realisticLevel = INVALID, - realisticStrings = StringDefinitions.INVALID_FLOW - ) - }) - public void fromStringArrayLowSoundness(int index) { - String[] classes = { - "java.lang.String", "java.lang.StringBuilder", - "java.lang.System", "java.lang.Runnable" - }; - if (index >= 0 && index < classes.length) { - analyzeString(classes[index]); - } - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "array access with unknown index", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) - }) - 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) { - analyzeString(classes[index]); - } - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a case where an array access needs to be interpreted with multiple static and virtual function calls", - stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|.*|java.lang.Integer)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) - }) - public void arrayStaticAndVirtualFunctionCalls(int i) { - String[] classes = { - "java.lang.Object", - getRuntimeClassName(), - StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), - System.getProperty("SomeClass") - }; - analyzeString(classes[i]); - } - - @StringDefinitionsCollection( - value = "a simple case where multiple definition sites have to be considered", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(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); - } - - @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()); - } - - @StringDefinitionsCollection( - value = "a set of tests that test compatibility with ternary operators", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Some|SomeOther)"), - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(.*|Some)"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(SomeOther|Some.*)") - }) - public void ternaryOperators(boolean flag, String param) { - String s1 = "Some"; - String s2 = s1 + "Other"; - - analyzeString(flag ? s1 : s2); - analyzeString(flag ? s1 : param); - analyzeString(flag ? s1 + param : s2); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a more comprehensive case where multiple definition sites have to be " - + "considered each with a different string generation mechanism", - stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "((java.lang.Object|.*)|.*|java.lang.System|java.lang..*)", - realisticLevel = DYNAMIC, - // Array values are currently not interpreted - realisticStrings = "(.*|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); - } - - @StringDefinitionsCollection( - value = "Switch statement with multiple relevant and multiple irrelevant cases", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac)") - }) - public void switchRelevantAndIrrelevant(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()); - } - - @StringDefinitionsCollection( - value = "Switch statement with multiple relevant and multiple irrelevant cases and a relevant default case", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|a|ad)") - }) - public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - case 2: - break; - case 3: - break; - default: - sb.append("d"); - break; - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "Switch statement with multiple relevant and multiple irrelevant cases and an irrelevant default case", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab|ac)") - }) - public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - case 2: - break; - case 3: - break; - default: - break; - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "Switch statement with multiple relevant and no irrelevant cases and a relevant default case", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|ad)") - }) - public void switchRelevantWithRelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - default: - sb.append("d"); - break; - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "Switch statement a relevant default case and a nested switch statement", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|a|ad|af)") - }) - public void switchNestedNoNestedDefault(int value, int value2) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - switch (value2) { - case 0: - sb.append("c"); - break; - case 1: - sb.append("d"); - break; - } - break; - default: - sb.append("f"); - break; - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "Switch statement a relevant default case and a nested switch statement", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac|ad|ae|af)") - }) - public void switchNestedWithNestedDefault(int value, int value2) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - switch (value2) { - case 0: - sb.append("c"); - break; - case 1: - sb.append("d"); - break; - default: - sb.append("e"); - break; - } - break; - default: - sb.append("f"); - break; - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "if-else control structure which append to a string builder with an int expr and an int", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(42-42|x)") - }) - public void ifElseWithStringBuilderWithIntExpr() { - StringBuilder sb1 = new StringBuilder(); - StringBuilder sb2 = new StringBuilder(); - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb1.append("x"); - sb2.append(42); - sb2.append(-42); - } else { - sb1.append(i + 1); - sb2.append("x"); - } - analyzeString(sb1.toString()); - 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.142.71828|^-?\\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 = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|b)"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(ab|ac)") - }) - public void ifElseWithStringBuilder1() { - StringBuilder sb1; - StringBuilder sb2 = new StringBuilder("a"); - - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb1 = new StringBuilder("a"); - sb2.append("b"); - } else { - sb1 = new StringBuilder("b"); - sb2.append("c"); - } - analyzeString(sb1.toString()); - analyzeString(sb2.toString()); - } - - @StringDefinitionsCollection( - value = "if-else control structure which appends to a string builder multiple times", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(abcd|axyz)") - }) - public void ifElseWithStringBuilder3() { - StringBuilder sb = new StringBuilder("a"); - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb.append("b"); - sb.append("c"); - sb.append("d"); - } else { - sb.append("x"); - sb.append("y"); - sb.append("z"); - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "if-else control structure which append to a string builder multiple times and a non used else if branch is present", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(abcd|a|axyz)") - }) - public void ifElseWithStringBuilder4() { - StringBuilder sb = new StringBuilder("a"); - int i = new Random().nextInt(); - if (i % 3 == 0) { - sb.append("b"); - sb.append("c"); - sb.append("d"); - } else if (i % 2 == 0) { - System.out.println("something"); - } else { - sb.append("x"); - sb.append("y"); - sb.append("z"); - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "simple for loops with known and unknown bounds", - stringDefinitions = { - // Currently, the analysis does not support determining loop ranges => a(b)* - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - public void simpleForLoopWithKnownBounds() { - StringBuilder sb = new StringBuilder("a"); - for (int i = 0; i < 10; i++) { - sb.append("b"); - } - analyzeString(sb.toString()); - - int limit = new Random().nextInt(); - sb = new StringBuilder("a"); - for (int i = 0; i < limit; i++) { - sb.append("b"); - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "if-else control structure within a for loop and with an append afterwards", - stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|^-?\\d+$))*yz", - realisticLevel = DYNAMIC, - realisticStrings = "(.*|.*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()); - } - - @StringDefinitionsCollection( - value = "if control structure without an else", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(a|ab)") - }) - public void ifWithoutElse() { - StringBuilder sb = new StringBuilder("a"); - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb.append("b"); - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "case with a nested loop where in the outer loop a StringBuilder is created " - + "that is later read", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - 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()); - } - } - - @StringDefinitionsCollection( - value = "some example that makes use of a StringBuffer instead of a StringBuilder", - stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "((x|^-?\\d+$))*yz", - realisticLevel = DYNAMIC, realisticStrings = "(.*|.*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()); - } - - @StringDefinitionsCollection( - value = "while-true example", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - public void whileWithBreak() { - StringBuilder sb = new StringBuilder("a"); - while (true) { - sb.append("b"); - if (sb.length() > 100) { - break; - } - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "an example with a non-while-true loop containing a break", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "a(b)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - 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()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "an extensive example with many control structures", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", - realisticLevel = DYNAMIC, realisticStrings = "((iv1|iv2): |.*)" - ), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(iv1|iv2): ((great!)?)*(.*)?", - realisticLevel = DYNAMIC, - realisticStrings = "(.*|.*.*)" - ) - }) - public void extensive(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()); - } - - analyzeString(sb.toString()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "an example with a throw (and no try-catch-finally)", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*") - }) - 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()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "case with a try-finally exception", - // Multiple string definition values are necessary because the three-address code contains multiple calls to - // 'analyzeString' which are currently not filtered out - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(File Content:|File Content:.*)"), - @StringDefinitions(expectedLevel = PARTIALLY_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()); - } - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "case with a try-catch-finally exception", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====.*"), - // Exception case without own thrown exception - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====)"), - // The following cases are detected: - // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 3) - // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 1) - // 3. First append succeeds, throws no exception -> only first append (Pos 4) - // 4. First append is executed but throws an exception Throwable -> both appends (Pos 2) - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(==========|=====.*=====|=====|=====.*)") - }) - 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()); - } - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "case with a try-catch-finally throwable", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:.*"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(BOS::EOS|BOS:.*:EOS)"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "(BOS::EOS|BOS:.*: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 = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") - }) - public void simpleClearExamples() { - StringBuilder sb1 = new StringBuilder("init_value:"); - sb1.setLength(0); - sb1.append("java.lang.StringBuilder"); - - StringBuilder sb2 = new StringBuilder("init_value:"); - System.out.println(sb2.toString()); - sb2 = new StringBuilder(); - sb2.append("java.lang.StringBuilder"); - - analyzeString(sb1.toString()); - analyzeString(sb2.toString()); - } - - @StringDefinitionsCollection( - value = "a more advanced example with a StringBuilder#setLength to clear it", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(Goodbye|init_value:Hello, world!Goodbye)" - ) - }) - public void advancedClearExampleWithSetLength(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()); - } - - @StringDefinitionsCollection( - value = "a simple and a little more advanced example with a StringBuilder#replace call", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(.*Goodbye|init_value:Hello, world!Goodbye)" - ) - }) - public void replaceExamples(int value) { - StringBuilder sb1 = new StringBuilder("init_value"); - sb1.replace(0, 5, "replaced_value"); - analyzeString(sb1.toString()); - - sb1 = new StringBuilder("init_value:"); - if (value < 10) { - sb1.replace(0, value, "..."); - } else { - sb1.append("Hello, world!"); - } - sb1.append("Goodbye"); - analyzeString(sb1.toString()); - } - - @StringDefinitionsCollection( - value = "loops that use breaks and continues (or both)", - stringDefinitions = { - @StringDefinitions( - // The bytecode produces an "if" within an "if" inside the first loop => two conditions - expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ), - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "", - realisticLevel = DYNAMIC, realisticStrings = "(.*|)" - ), - @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - 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()); - } - - @StringDefinitionsCollection( - value = "loops that use breaks and continues (or both)", - stringDefinitions = { - @StringDefinitions( - // The bytecode produces an "if" within an "if" inside the first loop, => two conditions - expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*", - realisticLevel = DYNAMIC, realisticStrings = ".*" - ) - }) - public void breakContinueExamples2(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()); - } - - @StringDefinitionsCollection( - value = "an example where in the condition of an 'if', a string is appended to a StringBuilder", - 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)) { - System.out.println("Yep, got the correct class!"); - } - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "checks if a string value with > 2 continuous appends and a second " - + "StringBuilder is determined correctly", - 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"); - sb.append('.').append("lang"); - sb2.append('.'); - sb.append("String"); - sb.append(sb2.toString()); - analyzeString(sb2.toString()); - analyzeString(sb.toString()); - } - - @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 = "java.lang.(Object|Runtime)") - }) - public void complexSecondStringBuilderRead(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()); - } - - @StringDefinitionsCollection( - value = "checks if the case, where the value of a StringBuilder depends on the " - + "simple construction of a second StringBuilder is determined correctly.", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Object|java.lang.Runtime)") - }) - public void simpleSecondStringBuilderRead(String className) { - StringBuilder sbObj = new StringBuilder("Object"); - StringBuilder sbRun = new StringBuilder("Runtime"); - - StringBuilder sb1 = new StringBuilder("java.lang."); - if (sb1.length() == 0) { - sb1.append(sbObj.toString()); - } else { - sb1.append(sbRun.toString()); - } - - analyzeString(sb1.toString()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @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 = DYNAMIC, expectedStrings = ".*") - }) - public void valueOfTest() { - analyzeString(String.valueOf('c')); - analyzeString(String.valueOf((float) 42.3)); - analyzeString(String.valueOf(getRuntimeClassName())); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "an example that uses a non final field", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:.*") - }) - public void nonFinalFieldRead() { - StringBuilder sb = new StringBuilder("Field Value:"); - System.out.println(sb); - sb.append(someStringField); - analyzeString(sb.toString()); - } - - @StringDefinitionsCollection( - value = "an example that reads a public final static field; for these, the string " - + "information are available (at least on modern compilers)", - stringDefinitions = { - @StringDefinitions(expectedLevel = 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()); - } - - @StringDefinitionsCollection( - value = "A case with a criss-cross append on two StringBuilders", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Object|ObjectRuntime)"), - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(RuntimeObject|Runtime)") - }) - 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 = "examples that use a passed parameter to define strings that are analyzed", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*"), - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*.*") - }) - 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()); - } - - @StringDefinitionsCollection( - value = "examples that use a passed parameter to define strings that are analyzed", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*") - }) - public void parameterRead2(String stringValue, StringBuilder sbValue) { - StringBuilder sb = new StringBuilder("value="); - System.out.println(sb.toString()); - sb.append(stringValue); - 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.*|s.*)"), - }) - 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 = "Some comprehensive example for experimental purposes taken from the JDK and " + - "slightly modified", - stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "Hello: (.*|.*|.*)?", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ), - }) - 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()); - } - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "an example with an unknown character read", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - }) - 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()); - } - - @StringDefinitionsCollection( - value = "a case where a static method with a string parameter is called", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Integer") - }) - public void fromStaticMethodWithParamTest() { - analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); - } - - @StringDefinitionsCollection( - value = "tests that the string analysis neatly integrates with the system properties analysis", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "some.test.value") - }) - public void systemPropertiesIntegrationTest() { - System.setProperty("some.test.property", "some.test.value"); - String s = System.getProperty("some.test.property"); - analyzeString(s); - } - - 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/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java deleted file mode 100644 index d380cfecd9..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/L1TestMethods.java +++ /dev/null @@ -1,681 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l1; - -import org.opalj.fpcf.fixtures.string_analysis.l0.StringProvider; -import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.GreetingService; -import org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies.HelloGreeting; -import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; -import org.opalj.fpcf.properties.string_analysis.*; - -import javax.management.remote.rmi.RMIServer; -import java.io.File; -import java.io.FileNotFoundException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Random; -import java.util.Scanner; - -import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; - -/** - * This file contains various tests for the L1StringAnalysis. For further information on what to consider, please see - * {@link L0TestMethods}. - * - * @author Patrick Mell - */ -public class L1TestMethods extends L0TestMethods { - - public static final String JAVA_LANG = "java.lang"; - private static final String rmiServerImplStubClassName = - RMIServer.class.getName() + "Impl_Stub"; - - private String myField; - - private String noWriteField; - - private Object myObject; - - private String fieldWithInit = "init field value"; - - private String fieldWithConstructorInit; - - private float secretNumber; - - public static String someKey = "will not be revealed here"; - - private String[] monthNames = { "January", "February", "March", getApril() }; - - /** - * {@see L0TestMethods#analyzeString} - */ - public void analyzeString(String s) { - } - - public L1TestMethods(float e) { - fieldWithConstructorInit = "initialized by constructor"; - secretNumber = e; - } - - @StringDefinitionsCollection( - value = "a case where a very simple non-virtual function call is interpreted", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime") - }) - public void simpleNonVirtualFunctionCallTest(int i) { - analyzeString(getRuntimeClassName()); - } - - @StringDefinitionsCollection( - value = "a case where a non-virtual function call inside an if statement is interpreted", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)") - }) - public void simpleNonVirtualFunctionCallTestWithIf(int i) { - String s; - if (i == 0) { - s = getRuntimeClassName(); - } else if (i == 1) { - s = getStringBuilderClassName(); - } else { - s = "ERROR"; - } - analyzeString(s); - } - - @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()); - } - - @AllowedSoundnessModes(SoundnessMode.LOW) - @StringDefinitionsCollection( - value = "a case where a static method is called that returns a string but are not " - + "within this project => cannot / will not be interpret", - stringDefinitions = { - @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW), - }) - public void staticMethodOutOfScopeLowSoundnessTest() { - analyzeString(System.clearProperty("os.version")); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a case where a static method is called that returns a string but are not " - + "within this project => cannot / will not be interpret", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), - }) - public void staticMethodOutOfScopeTest() { - analyzeString(System.clearProperty("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 = "(.*)*", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) - }) - 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()); - } - - @StringDefinitionsCollection( - value = "a case where function calls are involved in append operations", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "classname:StringBuilder,osname:someValue" - ) - }) - public void appendTest() { - StringBuilder sb = new StringBuilder("classname:"); - sb.append(getSimpleStringBuilderClassName()); - sb.append(",osname:"); - sb.append(StringProvider.getSomeValue()); - 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")); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", - stringDefinitions = { - @StringDefinitions(expectedLevel = PARTIALLY_CONSTANT, expectedStrings = ".*Impl_Stub") - }) - public void getStaticTest() { - analyzeString(rmiServerImplStubClassName); - } - - @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()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @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.*|getHello, Worldjava.lang.Runtime)" - ) - }) - public void dependenciesWithinFinalizeTest(String s, Class clazz) { - String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : - getHelloWorld() + getRuntimeClassName(); - String getterName = "get" + properName; - Method m; - try { - m = clazz.getMethod(getterName); - System.out.println(m); - analyzeString(getterName); - } catch (NoSuchMethodException var13) { - } - } - - @StringDefinitionsCollection( - value = "a function parameter being analyzed on its own", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Hello, World|my.helper.Class)") - }) - public String callerWithFunctionParameterTest(String s, float i) { - analyzeString(s); - return s; - } - - @StringDefinitionsCollection( - value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic is involved", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Hello, World") - }) - public void belongsToSomeTestCase() { - String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); - analyzeString(s); - } - - public void belongsToSomeTestCaseAnotherTime() { - callerWithFunctionParameterTest(belongsToTheSameTestCaseAnotherTime(), 900); - } - - /** - * Necessary for the callerWithFunctionParameterTest. - */ - public static String belongsToTheSameTestCase() { - return getHelloWorld(); - } - public static String belongsToTheSameTestCaseAnotherTime() { - return getHelperClass(); - } - - @StringDefinitionsCollection( - value = "a function parameter being analyzed on its own", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "(Hello, World|my.helper.Class)") - }) - public String callerWithFunctionParameterMultipleCallsInSameMethodTest(String s, float i) { - analyzeString(s); - return s; - } - - /** - * Necessary for the callerWithFunctionParameterMultipleCallsInSameMethodTest. - */ - public void belongsToSomeTestCase3() { - callerWithFunctionParameterMultipleCallsInSameMethodTest(belongsToTheSameTestCase(), 900); - callerWithFunctionParameterMultipleCallsInSameMethodTest(belongsToTheSameTestCaseAnotherTime(), 900); - } - - @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())); - } - - @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"); - } - - @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)?", - realisticLevel = CONSTANT, - // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - realisticStrings = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_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); - } - - /** - * Necessary for the tieName test. - */ - private static String tieNameForCompiler(String var0) { - return var0 + "_tie"; - } - - @StringDefinitionsCollection( - value = "a case where a string field is read", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(another value|some value|^null$)", - // Contains a field write in the same method which cannot be captured by flow functions - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) - }) - public void fieldReadTest() { - myField = "some value"; - analyzeString(myField); - } - - 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$)") - }) - public void fieldWithNoWriteTest() { - analyzeString(noWriteField); - } - - @AllowedSoundnessModes(SoundnessMode.LOW) - @StringDefinitionsCollection( - value = "a case where a field is read whose type is not supported", - stringDefinitions = { - @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) - }) - public void nonSupportedFieldTypeReadLowSoundness() { - analyzeString(myObject.toString()); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a case where a field is read whose type is not supported", - stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*") - }) - 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 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 = { - @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"; - } - } - - // On L1, this test is equivalent to the `severalReturnValuesTest1` - @AllowedDomainLevels(DomainLevel.L2) - @StringDefinitionsCollection( - value = "a case where the single valid return value of the called function can be resolved without calling the function", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "val", - // Since the virtual function return value is inlined in L2 and its actual runtime return - // value is not used, the function call gets converted to a method call, which modifies the - // TAC: The def PC from the `analyzeString` parameter is now different and points to the def - // PC for the `resolvableReturnValueFunction` parameter. This results in no string flow being - // detected since the def and use sites are now inconsistent. - realisticLevel = INVALID, realisticStrings = StringDefinitions.INVALID_FLOW - ) - }) - public void resolvableReturnValue() { - analyzeString(resolvableReturnValueFunction("val", 42)); - } - - /** - * Belongs to resolvableReturnValue. - */ - private String resolvableReturnValueFunction(String s, int i) { - switch (i) { - case 0: return getObjectClassName(); - case 1: return "One"; - default: return s; - } - } - - @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 "One"; - case 1: return s; - default: return getObjectClassName(); - } - } - - @StringDefinitionsCollection( - value = "a case where a 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(); - } - } - - @StringDefinitionsCollection( - value = "a case where a non-virtual and a static function have no return values at all", - stringDefinitions = { - @StringDefinitions(expectedLevel = INVALID, expectedStrings = StringDefinitions.INVALID_FLOW) - }) - public void functionWithNoReturnValueTest1() { - analyzeString(noReturnFunction1()); - } - - /** - * Belongs to functionWithNoReturnValueTest1. - */ - public String noReturnFunction1() { - throw new RuntimeException(); - } - - @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); - } - - @AllowedSoundnessModes(SoundnessMode.HIGH) - @StringDefinitionsCollection( - value = "a case where a String array field is read", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(January|February|March|April)", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ) - }) - public void getStringArrayField(int i) { - analyzeString(monthNames[i]); - } - - // DIFFERING TEST CASES FROM PREVIOUS LEVELS - - @StringDefinitionsCollection( - value = "a more comprehensive case where multiple definition sites have to be " - + "considered each with a different string generation mechanism", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "((java.lang.Object|java.lang.Runtime)|java.lang.System|java.lang.StringBuilder)", - realisticLevel = DYNAMIC, - // Array values are currently not interpreted. Also, differently constructed strings are - // currently not deduplicated since they result in different string trees during flow analysis. - realisticStrings = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)" - ) - }) - public void multipleDefSites(int value) {} - - @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() {} - - @StringDefinitionsCollection( - value = "an example that uses a non final field", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "Field Value:private l0 non-final string field") - }) - public void nonFinalFieldRead() {} - - @StringDefinitionsCollection( - value = "can handle virtual function calls", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") - }) - public void fromFunctionCallLowSoundness() {} - - @StringDefinitionsCollection( - value = "can handle virtual function calls", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") - }) - public void fromFunctionCall() {} - - @StringDefinitionsCollection( - value = "constant string + string from function call => CONSTANT", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") - }) - public void fromConstantAndFunctionCallLowSoundness() {} - - @StringDefinitionsCollection( - value = "constant string + string from function call => CONSTANT", - stringDefinitions = { - @StringDefinitions(expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder") - }) - public void fromConstantAndFunctionCall() {} - - @StringDefinitionsCollection( - value = "Some comprehensive example for experimental purposes taken from the JDK and slightly modified", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?", - realisticLevel = DYNAMIC, - realisticStrings = ".*" - ), - }) - protected void setDebugFlags(String[] var1) {} - - @StringDefinitionsCollection( - value = "an extensive example with many control structures", - stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): ", - // The real value is not fully resolved yet, since the string builder is used in a while loop, - // which leads to the string builder potentially carrying any value. This can be refined by - // recording pc specific states during data flow analysis. - realisticLevel = DYNAMIC, realisticStrings = "((iv1|iv2): |.*)" - ), - @StringDefinitions( - expectedLevel = CONSTANT, - expectedStrings = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?", - realisticLevel = DYNAMIC, - realisticStrings = "(.*|.*java.lang.Runtime)" - ) - }) - public void extensive(boolean cond) {} - - private String getRuntimeClassName() { - return "java.lang.Runtime"; - } - - private String getStringBuilderClassName() { - return "java.lang.StringBuilder"; - } - - private String getSimpleStringBuilderClassName() { - return "StringBuilder"; - } - - private static String getHelloWorld() { - return "Hello, World"; - } - - private static String addExclamationMark(String s) { - return s + "!"; - } - - private String addQuestionMark(String s) { - return s + "?"; - } - - private String getObjectClassName() { - return "java.lang.Object"; - } - - private static String getHelperClass() { - return "my.helper.Class"; - } - - private String getApril() { - return "April"; - } -} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java similarity index 68% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java index 7a01b47f17..4ed815d9b2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/GreetingService.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.tools; public interface GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java similarity index 77% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java index dec83fbf2a..ff1aafee41 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/HelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.tools; public class HelloGreeting implements GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java similarity index 77% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java index dddd15c907..a5e9fe3304 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l1/hierarchies/SimpleHelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l1.hierarchies; +package org.opalj.fpcf.fixtures.string_analysis.tools; public class SimpleHelloGreeting implements GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java similarity index 71% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java index b6835bbe5c..160633b43a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/l0/StringProvider.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java @@ -1,15 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.l0; +package org.opalj.fpcf.fixtures.string_analysis.tools; public class StringProvider { - /** - * Returns "[packageName].[className]". - */ - public static String getFQClassName(String packageName, String className) { - return packageName + "." + className; - } - /** * Returns "[packageName].[className]". */ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java new file mode 100644 index 0000000000..6ee7e130ed --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java @@ -0,0 +1,29 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +@PropertyValidator(key = "StringConstancy", validator = ConstantStringMatcher.class) +@Documented +@Repeatable(Constants.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD, ElementType.LOCAL_VARIABLE }) +public @interface Constant { + + int n(); + + String reason() default "N/A"; + + /** + * A regexp like string that describes the element(s) that are expected. + */ + String value(); + + Level[] levels() default { Level.TRUTH }; + + DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; + + SoundnessMode[] soundness() default { SoundnessMode.LOW, SoundnessMode.HIGH }; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java similarity index 68% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java index 2f96bd8b83..3a77c92313 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedDomainLevels.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java @@ -3,13 +3,10 @@ import java.lang.annotation.*; -/** - * @author Maximilian Rüsch - */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) -public @interface AllowedDomainLevels { +public @interface Constants { - DomainLevel[] value(); + Constant[] value(); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java new file mode 100644 index 0000000000..7429f2b586 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java @@ -0,0 +1,29 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +@PropertyValidator(key = "StringConstancy", validator = DynamicStringMatcher.class) +@Documented +@Repeatable(Dynamics.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface Dynamic { + + int n(); + + String reason() default "N/A"; + + /** + * A regexp like string that describes the element(s) that are expected. + */ + String value(); + + Level[] levels() default { Level.TRUTH }; + + DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; + + SoundnessMode[] soundness() default { SoundnessMode.LOW, SoundnessMode.HIGH }; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java similarity index 67% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java index ee0eefa867..cc5095d25c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/AllowedSoundnessModes.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java @@ -3,13 +3,10 @@ import java.lang.annotation.*; -/** - * @author Maximilian Rüsch - */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) -public @interface AllowedSoundnessModes { +public @interface Dynamics { - SoundnessMode[] value(); + Dynamic[] value(); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java new file mode 100644 index 0000000000..a76651ac30 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +@Documented +@Repeatable(Failures.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface Failure { + + int n(); + + Level[] levels(); + + String reason() default "N/A"; + + DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java new file mode 100644 index 0000000000..7e9403d74a --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java @@ -0,0 +1,12 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface Failures { + + Failure[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java new file mode 100644 index 0000000000..43bb2c7c57 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +@PropertyValidator(key = "StringConstancy", validator = InvalidStringMatcher.class) +@Documented +@Repeatable(Invalids.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface Invalid { + + int n(); + + String reason() default "N/A"; + + Level[] levels() default { Level.TRUTH }; + + DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; + + SoundnessMode[] soundness() default { SoundnessMode.LOW, SoundnessMode.HIGH }; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java new file mode 100644 index 0000000000..75ad3fa42d --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java @@ -0,0 +1,12 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface Invalids { + + Invalid[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java new file mode 100644 index 0000000000..99477f8120 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java @@ -0,0 +1,22 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +/** + * @author Maximilian Rüsch + */ +public enum Level { + + TRUTH("TRUTH"), + L0("L0"), + L1("L1"); + + private final String value; + + Level(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java new file mode 100644 index 0000000000..b8a64f9138 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java @@ -0,0 +1,29 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +@PropertyValidator(key = "StringConstancy", validator = PartiallyConstantStringMatcher.class) +@Documented +@Repeatable(PartiallyConstants.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface PartiallyConstant { + + int n(); + + String reason() default "N/A"; + + /** + * A regexp like string that describes the element(s) that are expected. + */ + String value(); + + Level[] levels() default { Level.TRUTH }; + + DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; + + SoundnessMode[] soundness() default { SoundnessMode.LOW, SoundnessMode.HIGH }; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java new file mode 100644 index 0000000000..170407260d --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java @@ -0,0 +1,12 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_analysis; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface PartiallyConstants { + + PartiallyConstant[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java deleted file mode 100644 index fb6c811138..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java +++ /dev/null @@ -1,30 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; - -/** - * Java annotations do not work with Scala enums, such as - * {@link org.opalj.br.fpcf.properties.string.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"), - // Added to enable leaving a string constancy level of a string definition unspecified - UNSPECIFIED("unspecified"), - // Added to enable specifying allowed invalid flow - INVALID("invalid"); - - 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_analysis/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java deleted file mode 100644 index 0369859cec..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java +++ /dev/null @@ -1,46 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; - -import org.opalj.fpcf.fixtures.string_analysis.l0.L0TestMethods; -import org.opalj.fpcf.properties.PropertyValidator; - -import java.lang.annotation.*; - -/** - * 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 constant 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 - * 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 Maximilian Rüsch - */ -@PropertyValidator(key = "StringConstancy", validator = StringAnalysisMatcher.class) -@Documented -@Retention(RetentionPolicy.CLASS) -@Target({ ElementType.ANNOTATION_TYPE }) -public @interface StringDefinitions { - - String NO_STRINGS = "N/A"; - String INVALID_FLOW = ""; - - /** - * This value determines the expected level of freedom for a local variable to - * be changed. - */ - StringConstancyLevel expectedLevel(); - StringConstancyLevel realisticLevel() default StringConstancyLevel.UNSPECIFIED; - - /** - * A regexp like string that describes the element(s) that are expected. For the rules, refer to - * {@link L0TestMethods}. - * 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 realisticStrings() default NO_STRINGS; -} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java deleted file mode 100644 index 72d3cf307d..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java +++ /dev/null @@ -1,27 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; - -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 Maximilian Rüsch - */ -@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/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 65e44deb36..e0f928f8ea 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 @@ -12,8 +12,12 @@ import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse import org.opalj.ai.fpcf.properties.AIDomainFactoryKey import org.opalj.br.Annotation import org.opalj.br.Annotations +import org.opalj.br.ElementValue +import org.opalj.br.ElementValuePair +import org.opalj.br.ElementValuePairs import org.opalj.br.Method import org.opalj.br.ObjectType +import org.opalj.br.StringValue import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.br.analyses.Project @@ -21,7 +25,18 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.fpcf.properties.string_analysis.Constant +import org.opalj.fpcf.properties.string_analysis.Constants import org.opalj.fpcf.properties.string_analysis.DomainLevel +import org.opalj.fpcf.properties.string_analysis.Dynamic +import org.opalj.fpcf.properties.string_analysis.Dynamics +import org.opalj.fpcf.properties.string_analysis.Failure +import org.opalj.fpcf.properties.string_analysis.Failures +import org.opalj.fpcf.properties.string_analysis.Invalid +import org.opalj.fpcf.properties.string_analysis.Invalids +import org.opalj.fpcf.properties.string_analysis.Level +import org.opalj.fpcf.properties.string_analysis.PartiallyConstant +import org.opalj.fpcf.properties.string_analysis.PartiallyConstants import org.opalj.fpcf.properties.string_analysis.SoundnessMode import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV @@ -42,7 +57,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { // The name of the method from which to extract PUVars to analyze. val nameTestMethod: String = "analyzeString" - def level: Int + def level: Level def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] def domainLevel: DomainLevel def soundnessMode: SoundnessMode @@ -57,10 +72,13 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { .withValue(InterpretationHandler.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) } + override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string_analysis") + override final def init(p: Project[URL]): Unit = { val domain = domainLevel match { case DomainLevel.L1 => classOf[DefaultDomainWithCFGAndDefUse[_]] case DomainLevel.L2 => classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + case _ => throw new IllegalArgumentException(s"Invalid domain level for test definition: $domainLevel") } p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { case None => Set(domain) @@ -95,61 +113,15 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } } - override def fixtureProjectPackage: List[String] = { - StringAnalysisTest.getFixtureProjectPackages(level).toList - } - - protected def allowedFQTestMethodsClassNames: Iterable[String] = { - StringAnalysisTest.getAllowedFQTestMethodClassNamesUntilLevel(level) - } - - /** - * Resolves all test methods for this [[level]] and below while taking overrides into account. For all test methods, - * [[extractPUVars]] is called with their [[TACode]]. - * - * @return An [[Iterable]] containing the [[VariableContext]] to be analyzed and the method that has the relevant - * annotations attached. - */ def determineEntitiesToAnalyze(project: Project[URL]): Iterable[(VariableContext, Method)] = { val tacProvider = project.get(EagerDetachedTACAIKey) val declaredMethods = project.get(DeclaredMethodsKey) val contextProvider = project.get(ContextProviderKey) - project.classHierarchy.allSuperclassesIterator( - ObjectType(StringAnalysisTest.getAllowedFQTestMethodObjectTypeNameForLevel(level)), - reflexive = true - )(project).toList - .filter(_.thisType.packageName.startsWith("org/opalj/fpcf/fixtures/string_analysis/")) - .sortBy { cf => cf.thisType.simpleName.substring(1, 2).toInt } - .foldLeft(Map.empty[Method, Method]) { (implementationsToAnnotations, cf) => - implementationsToAnnotations ++ cf.methods.map { m => - ( - implementationsToAnnotations.find(kv => - kv._1.name == m.name && kv._1.descriptor == m.descriptor - ).map(_._1).getOrElse(m), - m - ) - } - } - .filter { - _._1.runtimeInvisibleAnnotations.foldLeft(false)((exists, a) => - exists || StringAnalysisTest.isStringUsageAnnotation(a) - ) - } - .filter { - _._1.runtimeInvisibleAnnotations.forall { a => - val r = isAllowedDomainLevel(a) - r.isEmpty || r.get - } - } - .filter { - _._1.runtimeInvisibleAnnotations.forall { a => - val r = isAllowedSoundnessMode(a) - r.isEmpty || r.get - } - } + project.allMethods + .filter(_.runtimeInvisibleAnnotations.nonEmpty) .foldLeft(Seq.empty[(VariableContext, Method)]) { (entities, m) => - entities ++ extractPUVars(tacProvider(m._1)).map(e => - (VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m._1))), m._2) + entities ++ extractPUVars(tacProvider(m)).map(e => + (VariableContext(e._1, e._2, contextProvider.newContext(declaredMethods(m))), m) ) } } @@ -162,9 +134,8 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { */ def extractPUVars(tac: TACode[TACMethodParameter, V]): List[(Int, PV)] = { tac.cfg.code.instructions.filter { - case VirtualMethodCall(_, declClass, _, name, _, _, _) => - allowedFQTestMethodsClassNames.exists(_ == declClass.toJavaClass.getName) && name == nameTestMethod - case _ => false + case VirtualMethodCall(_, _, _, name, _, _, _) => name == nameTestMethod + case _ => false }.map { call => (call.pc, call.asVirtualMethodCall.params.head.asVar.toPersistentForm(tac.stmts)) }.toList } @@ -177,79 +148,96 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { ).toMap // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => + val annotationsByIndex = getCheckableAnnotationsByIndex(project, am._3) m2e(am._1)._2.zipWithIndex.map { case (vc, index) => Tuple3( vc, { s: String => s"${am._2(s)} (#$index)" }, - List(StringAnalysisTest.getStringDefinitionsFromCollection(am._3, index)) + annotationsByIndex(index).toList ) } } } - def isAllowedDomainLevel(a: Annotation): Option[Boolean] = { - if (a.annotationType.toJava != "org.opalj.fpcf.properties.string_analysis.AllowedDomainLevels") None - else Some { - a.elementValuePairs.head.value.asArrayValue.values.exists { v => - DomainLevel.valueOf(v.asEnumValue.constName) == domainLevel - } + private def getCheckableAnnotationsByIndex(project: Project[URL], as: Annotations): Map[Int, Annotations] = { + def mapFailure(failureEvp: ElementValuePairs): Annotation = { + if (soundnessMode == SoundnessMode.HIGH) + new Annotation( + ObjectType(classOf[Dynamic].getName.replace(".", "/")), + failureEvp.appended(ElementValuePair("value", StringValue(".*"))) + ) + else + new Annotation( + ObjectType(classOf[Invalid].getName.replace(".", "/")), + failureEvp + ) } - } - def isAllowedSoundnessMode(a: Annotation): Option[Boolean] = { - if (a.annotationType.toJava != "org.opalj.fpcf.properties.string_analysis.AllowedSoundnessModes") None - else Some { - a.elementValuePairs.head.value.asArrayValue.values.exists { v => - SoundnessMode.valueOf(v.asEnumValue.constName) == soundnessMode - } - } - } -} + as.flatMap { + case a @ Annotation(annotationType, evp) + if annotationType.toJavaClass == classOf[Constant] + || annotationType.toJavaClass == classOf[PartiallyConstant] + || annotationType.toJavaClass == classOf[Dynamic] + || annotationType.toJavaClass == classOf[Invalid] => + Seq((evp.head.value.asIntValue.value, a)) + + case Annotation(annotationType, evp) if annotationType.toJavaClass == classOf[Failure] => + Seq((evp.head.value.asIntValue.value, mapFailure(evp))) + + case Annotation(annotationType, evp) + if annotationType.toJavaClass == classOf[Constants] + || annotationType.toJavaClass == classOf[PartiallyConstants] + || annotationType.toJavaClass == classOf[Dynamics] + || annotationType.toJavaClass == classOf[Invalids] => + evp.head.value.asArrayValue.values.toSeq.map { av => + val annotation = av.asAnnotationValue.annotation + (annotation.elementValuePairs.head.value.asIntValue.value, annotation) + } -object StringAnalysisTest { + case Annotation(annotationType, evp) if annotationType.toJavaClass == classOf[Failures] => + evp.head.value.asArrayValue.values.toSeq.map { av => + val annotation = av.asAnnotationValue.annotation + (annotation.elementValuePairs.head.value.asIntValue.value, mapFailure(annotation.elementValuePairs)) + } - def getFixtureProjectPackages(level: Int): Seq[String] = { - Range.inclusive(0, level).map(l => s"org/opalj/fpcf/fixtures/string_analysis/l$l") + case _ => + Seq.empty + }.groupBy(_._1).map { kv => + val annotations = kv._2.map(_._2) + .filter(fulfillsDomainLevel(project, _, domainLevel)) + .filter(fulfillsSoundness(project, _, soundnessMode)) + + val matchingCurrentLevel = annotations.filter(fulfillsLevel(project, _, level)) + if (matchingCurrentLevel.isEmpty) { + (kv._1, annotations.filter(fulfillsLevel(project, _, Level.TRUTH))) + } else { + (kv._1, matchingCurrentLevel) + } + } } - def getAllowedFQTestMethodClassNamesUntilLevel(level: Int): Seq[String] = { - Range.inclusive(0, level).map(l => s"org.opalj.fpcf.fixtures.string_analysis.l$l.L${l}TestMethods") + private def fulfillsLevel(p: Project[URL], a: Annotation, l: Level): Boolean = { + getValue(p, a, "levels").asArrayValue.values.exists(v => Level.valueOf(v.asEnumValue.constName) == l) } - def getAllowedFQTestMethodObjectTypeNameForLevel(level: Int): String = { - s"org/opalj/fpcf/fixtures/string_analysis/l$level/L${level}TestMethods" + private def fulfillsDomainLevel(p: Project[URL], a: Annotation, dl: DomainLevel): Boolean = { + getValue(p, a, "domains").asArrayValue.values.exists(v => DomainLevel.valueOf(v.asEnumValue.constName) == dl) } - /** - * 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.toJava == "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" - - /** - * 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. - */ - def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = { - val collectionOpt = a.find(isStringUsageAnnotation) - if (collectionOpt.isEmpty) { - throw new IllegalArgumentException( - "Tried to collect string definitions from method that does not define them!" - ) + private def fulfillsSoundness(p: Project[URL], a: Annotation, soundness: SoundnessMode): Boolean = { + getValue(p, a, "soundness").asArrayValue.values.exists { v => + SoundnessMode.valueOf(v.asEnumValue.constName) == soundness } + } - collectionOpt.get.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation + private def getValue(p: Project[URL], a: Annotation, name: String): ElementValue = { + a.elementValuePairs.collectFirst { + case ElementValuePair(`name`, value) => value + }.orElse { + // get default value ... + p.classFile(a.annotationType.asObjectType).get.findMethod(name).head.annotationDefault + }.get } } @@ -261,7 +249,7 @@ object StringAnalysisTest { */ sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { - override final def level = 0 + override final def level = Level.L0 override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL0StringAnalysis.allRequiredAnalyses :+ @@ -301,7 +289,7 @@ class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnaly */ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { - override def level = 1 + override def level = Level.L1 override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL1StringAnalysis.allRequiredAnalyses :+ diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala deleted file mode 100644 index c37be71b99..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala +++ /dev/null @@ -1,90 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package fpcf -package properties -package string_analysis - -import org.opalj.br.AnnotationLike -import org.opalj.br.ObjectType -import org.opalj.br.analyses.Project -import org.opalj.br.fpcf.properties.string.StringConstancyProperty - -/** - * @author Maximilian Rüsch - */ -class StringAnalysisMatcher extends AbstractPropertyMatcher { - - private def getLevelValue(a: AnnotationLike, valueName: String, optional: Boolean): StringConstancyLevel = { - a.elementValuePairs.find(_.name == valueName) match { - case Some(el) => StringConstancyLevel.valueOf(el.value.asEnumValue.constName) - case None if !optional => throw new IllegalArgumentException(s"Could not find $valueName in annotation $a") - case None => StringConstancyLevel.UNSPECIFIED - } - } - - private def getStringsValue(a: AnnotationLike, valueName: String, optional: Boolean): String = { - a.elementValuePairs.find(_.name == valueName) match { - case Some(el) => el.value.asStringValue.value - case None if !optional => throw new IllegalArgumentException(s"Could not find $valueName in annotation $a") - case None => StringDefinitions.NO_STRINGS - } - } - - /** - * @inheritdoc - */ - override def validateProperty( - p: Project[_], - as: Set[ObjectType], - entity: Any, - a: AnnotationLike, - properties: Iterable[Property] - ): Option[String] = { - if ( - a.annotationType.asObjectType != ObjectType("org/opalj/fpcf/properties/string_analysis/StringDefinitions") - ) { - throw new IllegalArgumentException( - "Can only extract the constancy level from a @StringDefinitions annotation" - ) - } - - val realisticLevel = getLevelValue(a, "realisticLevel", optional = true) - val realisticStrings = getStringsValue(a, "realisticStrings", optional = true) - val expectedLevel = getLevelValue(a, "expectedLevel", optional = false) - val expectedStrings = getStringsValue(a, "expectedStrings", optional = false) - - if (realisticLevel == expectedLevel && realisticStrings == expectedStrings) { - throw new IllegalStateException("Invalid test definition: Realistic and expected values are equal") - } else if ( - realisticLevel == StringConstancyLevel.UNSPECIFIED ^ realisticStrings == StringDefinitions.NO_STRINGS - ) { - throw new IllegalStateException( - "Invalid test definition: Realistic values must either be fully specified or be absent" - ) - } - - val testRealisticValues = - realisticLevel != StringConstancyLevel.UNSPECIFIED && realisticStrings != StringDefinitions.NO_STRINGS - val (testedLevel, testedStrings) = - if (testRealisticValues) (realisticLevel.toString.toLowerCase, realisticStrings) - else (expectedLevel.toString.toLowerCase, expectedStrings) - - val (actLevel, actString) = properties.head match { - case prop: StringConstancyProperty => - val tree = prop.sci.tree.simplify - if (tree.isInvalid) { - (StringConstancyLevel.INVALID.toString.toLowerCase, StringDefinitions.INVALID_FLOW) - } else { - (tree.constancyLevel.toString.toLowerCase, tree.toRegex) - } - case _ => ("", "") - } - - if (testedLevel != actLevel || testedStrings != actString) { - Some(s"Level: $testedLevel, Strings: $testedStrings") - } else { - None - } - } - -} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala new file mode 100644 index 0000000000..61009a51ea --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala @@ -0,0 +1,75 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package properties +package string_analysis + +import org.opalj.br.AnnotationLike +import org.opalj.br.ObjectType +import org.opalj.br.analyses.Project +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringConstancyProperty + +/** + * @author Maximilian Rüsch + */ +sealed trait StringMatcher extends AbstractPropertyMatcher { + + protected def getActualValues: Property => Option[(String, String)] = { + case prop: StringConstancyProperty => + val tree = prop.sci.tree.simplify + if (tree.isInvalid) { + None + } else { + Some((tree.constancyLevel.toString.toLowerCase, tree.toRegex)) + } + case p => throw new IllegalArgumentException(s"Tried to extract values from non string property: $p") + } +} + +sealed abstract class ConstancyStringMatcher(val constancyLevel: StringConstancyLevel.Value) extends StringMatcher { + + override def validateProperty( + p: Project[_], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedConstancy = constancyLevel.toString.toLowerCase + val expectedStrings = a.elementValuePairs.find(_.name == "value").get.value.asStringValue.value + val actualValuesOpt = getActualValues(properties.head) + if (actualValuesOpt.isEmpty) { + Some(s"Level: $expectedConstancy, Strings: $expectedStrings") + } else { + val (actLevel, actString) = actualValuesOpt.get + if (expectedConstancy != actLevel || expectedStrings != actString) { + Some(s"Level: $expectedConstancy, Strings: $expectedStrings") + } else { + None + } + } + } +} + +class ConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.CONSTANT) +class PartiallyConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.PARTIALLY_CONSTANT) +class DynamicStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.DYNAMIC) + +class InvalidStringMatcher extends StringMatcher { + + override def validateProperty( + p: Project[_], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val actualValuesOpt = getActualValues(properties.head) + if (actualValuesOpt.isDefined) { + Some(s"Invalid flow - No strings determined!") + } else { + None + } + } +} From 860f6a4da23e7e5f2d6836a4c114d64d29200770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 29 Jul 2024 21:57:07 +0200 Subject: [PATCH 506/583] Flatten call stack for handling new callers --- .../fpcf/analyses/string/StringAnalysis.scala | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 3b666e1096..a9609e77ae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -5,12 +5,15 @@ package fpcf package analyses package string +import scala.collection.mutable.ListBuffer + import org.opalj.br.DeclaredMethod import org.opalj.br.Method import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.NoCallers import org.opalj.br.fpcf.properties.string.StringConstancyInformation @@ -135,16 +138,21 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly oldCallers: Callers, newCallers: Callers )(implicit state: ContextStringAnalysisState): Unit = { + val relevantCallerContexts = ListBuffer.empty[(Context, Int)] newCallers.forNewCallerContexts(oldCallers, state.dm) { (_, callerContext, pc, _) => if (callerContext.hasContext && callerContext.method.hasSingleDefinedMethod) { - val callerMethod = callerContext.method.definedMethod + relevantCallerContexts.append((callerContext, pc)) + } + } - val tacEOptP = ps(callerMethod, TACAI.key) - state.registerTacaiDepender(tacEOptP, (callerContext, pc)) + for { (context, pc) <- relevantCallerContexts } { + val callerMethod = context.method.definedMethod - if (tacEOptP.hasUBP) { - handleTACAI(callerMethod, tacEOptP.ub) - } + val tacEOptP = ps(callerMethod, TACAI.key) + state.registerTacaiDepender(tacEOptP, (context, pc)) + + if (tacEOptP.hasUBP) { + handleTACAI(callerMethod, tacEOptP.ub) } } } @@ -187,6 +195,12 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly } private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { + if (state.dm.name == "newInstance" && state.dm.declaringClassType.fqn.endsWith("FactoryFinder")) { + System.out.println("DAMN") + } else if (state.dm.name == "find" && state.dm.declaringClassType.fqn.endsWith("FactoryFinder")) { + System.out.println("DAMN2") + } + if (state.hasDependees) { InterimResult( state.entity, From 15875f265eff0da113b13b54afbb0f65bf5935ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 5 Aug 2024 19:13:17 +0200 Subject: [PATCH 507/583] Raise all levels by one and add L0 for handling const values --- .../info/StringAnalysisReflectiveCalls.scala | 4 +- .../fixtures/string_analysis/ArrayOps.java | 8 +- .../fixtures/string_analysis/Complex.java | 15 +- .../ExceptionalControlStructures.java | 27 +- .../fixtures/string_analysis/External.java | 58 ++-- .../string_analysis/FunctionCalls.java | 64 +++-- .../fixtures/string_analysis/Integration.java | 11 +- .../fpcf/fixtures/string_analysis/Loops.java | 47 ++-- .../SimpleControlStructures.java | 27 +- .../SimpleStringBuilderOps.java | 53 ++-- .../string_analysis/SimpleStringOps.java | 67 +++-- .../properties/string_analysis/Constant.java | 2 +- .../properties/string_analysis/Dynamic.java | 2 +- .../properties/string_analysis/Failure.java | 5 +- .../properties/string_analysis/Invalid.java | 2 +- .../properties/string_analysis/Level.java | 3 +- .../string_analysis/PartiallyConstant.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 65 +++-- .../analyses/string/ComputationState.scala | 24 +- .../BinaryExprInterpreter.scala | 1 + .../L0InterpretationHandler.scala | 29 +- .../L0VirtualFunctionCallInterpreter.scala | 217 --------------- .../SimpleValueConstExprInterpreter.scala | 1 + .../analyses/string/l1/L1StringAnalysis.scala | 21 +- .../L1FunctionCallInterpreter.scala} | 4 +- .../L1InterpretationHandler.scala | 49 +--- ...L1NonVirtualFunctionCallInterpreter.scala} | 6 +- .../L1NonVirtualMethodCallInterpreter.scala} | 4 +- .../L1StaticFunctionCallInterpreter.scala} | 16 +- .../L1SystemPropertiesInterpreter.scala} | 4 +- .../L1VirtualFunctionCallInterpreter.scala | 263 ++++++++++++------ .../L1VirtualMethodCallInterpreter.scala} | 4 +- .../analyses/string/l2/L2StringAnalysis.scala | 48 ++++ .../L2FieldReadInterpreter.scala} | 12 +- .../L2InterpretationHandler.scala | 103 +++++++ .../L2VirtualFunctionCallInterpreter.scala | 136 +++++++++ 36 files changed, 801 insertions(+), 603 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{ => l0}/interpretation/BinaryExprInterpreter.scala (99%) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{ => l0}/interpretation/SimpleValueConstExprInterpreter.scala (99%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0FunctionCallInterpreter.scala => l1/interpretation/L1FunctionCallInterpreter.scala} (99%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala => l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala} (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0NonVirtualMethodCallInterpreter.scala => l1/interpretation/L1NonVirtualMethodCallInterpreter.scala} (95%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0StaticFunctionCallInterpreter.scala => l1/interpretation/L1StaticFunctionCallInterpreter.scala} (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0SystemPropertiesInterpreter.scala => l1/interpretation/L1SystemPropertiesInterpreter.scala} (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l0/interpretation/L0VirtualMethodCallInterpreter.scala => l1/interpretation/L1VirtualMethodCallInterpreter.scala} (97%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l1/interpretation/L1FieldReadInterpreter.scala => l2/interpretation/L2FieldReadInterpreter.scala} (97%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.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 index 4432df9f82..e153dc60bf 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 @@ -48,7 +48,7 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis -import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis import org.opalj.tac.fpcf.properties.TACAI import org.opalj.util.PerformanceEvaluation.time @@ -77,7 +77,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { def analyses: Seq[FPCFLazyAnalysisScheduler] = { if (runL0Analysis) LazyL0StringAnalysis.allRequiredAnalyses - else LazyL1StringAnalysis.allRequiredAnalyses + else LazyL2StringAnalysis.allRequiredAnalyses } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java index 27c2825e5b..90b862aa10 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java @@ -16,8 +16,8 @@ public class ArrayOps { */ public void analyzeString(String s) {} - @Constant(n = 0, value = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)", levels = Level.TRUTH) - @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "array accesses cannot be interpreted yet") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "array accesses cannot be interpreted yet") public void fromStringArray(int index) { String[] classes = { "java.lang.String", "java.lang.StringBuilder", @@ -29,7 +29,7 @@ public void fromStringArray(int index) { } @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)") - @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "arrays are not supported") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "arrays are not supported") public void arrayStaticAndVirtualFunctionCalls(int i) { String[] classes = { "java.lang.Object", @@ -41,7 +41,7 @@ public void arrayStaticAndVirtualFunctionCalls(int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(January|February|March|April)") - @Failure(n = 0, levels = { Level.L0, Level.L1 }, reason = "arrays are not supported") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "arrays are not supported") public void getStringArrayField(int i) { analyzeString(monthNames[i]); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java index d8df07c765..63c058649d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java @@ -19,7 +19,8 @@ public void analyzeString(String s) {} /** * Extracted from com.oracle.webservices.internal.api.message.BasePropertySet, has two def-sites and one use-site */ - @PartiallyConstant(n = 0, value = "(set.*|s.*)") + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(set.*|s.*)") + @Failure(n = 0, levels = Level.L0) public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { String name = getName; String setName = name.startsWith("is") ? @@ -40,8 +41,9 @@ public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException /** * Taken from com.sun.javafx.property.PropertyReference#reflect. */ - @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, World.*)") - @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, Worldjava.lang.Runtime)") + @Failure(n = 0, levels = Level.L0) + @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, World.*)") + @PartiallyConstant(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, Worldjava.lang.Runtime)") public void complexDependencyResolve(String s, Class clazz) { String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : getHelloWorld() + getRuntimeClassName(); @@ -59,8 +61,9 @@ public void complexDependencyResolve(String s, Class clazz) { * Taken from com.sun.prism.impl.ps.BaseShaderContext#getPaintShader and slightly adapted */ @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") + @Failure(n = 0, levels = Level.L0) // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - @Constant(n = 0, levels = { Level.L0, Level.L1 }, value = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest)") + @Constant(n = 0, levels = { Level.L1, Level.L2 }, value = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest)") public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { String shaderName = getHelloWorld() + "_" + "paintname"; if (getPaintType) { @@ -91,8 +94,8 @@ public void unknownCharValue() { analyzeString(sb.toString()); } - @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = Level.L1, value = "value") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Constant(n = 0, levels = Level.L2, value = "value") public String cyclicDependencyTest(String s) { String value = getProperty(s); analyzeString(value); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java index 3dbe548f90..02cfd3b2ec 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java @@ -17,9 +17,12 @@ public class ExceptionalControlStructures { public void analyzeString(String s) {} // Multiple calls to "analyzeString" are generated by the Java compiler, hence we have multiple definitions - @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "File Content:.*") - @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") - @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + @Failure(n = 0, levels = Level.L0) + @Failure(n = 1, levels = Level.L0) + @Failure(n = 2, levels = Level.L0) + @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "File Content:.*") + @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") public void tryFinally(String filename) { StringBuilder sb = new StringBuilder("File Content:"); try { @@ -31,15 +34,18 @@ public void tryFinally(String filename) { } } - @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "=====.*") + @Failure(n = 0, levels = Level.L0) + @Failure(n = 1, levels = Level.L0) + @Failure(n = 2, levels = Level.L0) + @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "=====.*") // Exception case without own thrown exception - @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====)") + @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====)") // The following cases are detected: // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 3) // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 1) // 3. First append succeeds, throws no exception -> only first append (Pos 4) // 4. First append is executed but throws an exception Throwable -> both appends (Pos 2) - @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====|=====|=====.*)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====|=====|=====.*)") public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); try { @@ -52,9 +58,12 @@ public void tryCatchFinally(String filename) { } } - @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = "BOS:.*") - @PartiallyConstant(n = 1, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") - @PartiallyConstant(n = 2, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") + @Failure(n = 0, levels = Level.L0) + @Failure(n = 1, levels = Level.L0) + @Failure(n = 2, levels = Level.L0) + @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "BOS:.*") + @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") public void tryCatchFinallyWithThrowable(String filename) { StringBuilder sb = new StringBuilder("BOS:"); try { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index d56ca405b8..4052024c7a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -35,22 +35,19 @@ public External(float e) { public void analyzeString(String s) {} @Constant(n = 0, levels = Level.TRUTH, value = "Field Value:private l0 non-final string field") - @Invalid(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW) - @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "Field Value:.*") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void nonFinalFieldRead() { - StringBuilder sb = new StringBuilder("Field Value:"); - System.out.println(sb); - sb.append(nonFinalNonStaticField); - analyzeString(sb.toString()); + analyzeString(nonFinalNonStaticField); } - @Constant(n = 0, value = "will not be revealed here") - @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.TRUTH, value = "will not be revealed here") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void nonFinalStaticFieldRead() { analyzeString(nonFinalStaticField); } - @Constant(n = 0, value = "Field Value:mine") + @Constant(n = 0, levels = Level.TRUTH, value = "Field Value:mine") + @Failure(n = 0, levels = Level.L0) public void publicFinalStaticFieldRead() { StringBuilder sb = new StringBuilder("Field Value:"); System.out.println(sb); @@ -58,28 +55,29 @@ public void publicFinalStaticFieldRead() { analyzeString(sb.toString()); } - @Constant(n = 0, value = "init field value") - @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.TRUTH, value = "init field value") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void fieldWithInitRead() { analyzeString(fieldWithSelfInit.toString()); } - @PartiallyConstant(n = 0, soundness = SoundnessMode.HIGH, value = ".*Impl_Stub") - @Failure(n = 0, levels = Level.L0) + @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*Impl_Stub") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void fieldWithInitWithOutOfScopeRead() { analyzeString(fieldWithSelfInitWithOutOfScopeCall); } - @Constant(n = 0, value = "initialized by constructor") - @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.TRUTH, value = "initialized by constructor") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void fieldInitByConstructorRead() { analyzeString(fieldWithConstructorInit.toString()); } @Dynamic(n = 0, levels = Level.TRUTH, value = "^-?\\d*\\.{0,1}\\d+$") - @Failure(n = 0, levels = Level.L0, domains = DomainLevel.L1) - @Invalid(n = 0, levels = Level.L0, domains = DomainLevel.L2, soundness = SoundnessMode.LOW) - @Dynamic(n = 0, levels = Level.L0, domains = DomainLevel.L2, soundness = SoundnessMode.HIGH, + @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = Level.L1, domains = DomainLevel.L1) + @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.LOW) + @Dynamic(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.HIGH, value = "^-?\\d*\\.{0,1}\\d+$", reason = "the field value is inlined using L2 domains") public void fieldInitByConstructorParameter() { analyzeString(new StringBuilder().append(fieldWithConstructorParameterInit).toString()); @@ -87,28 +85,31 @@ public void fieldInitByConstructorParameter() { // Contains a field write in the same method which cannot be captured by flow functions @Constant(n = 0, levels = Level.TRUTH, value = "(some value|^null$)") - @Failure(n = 0, levels = Level.L0) - @Dynamic(n = 0, levels = Level.L1, value = ".*") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Dynamic(n = 0, levels = Level.L2, value = ".*") public void fieldWriteInSameMethod() { writeInSameMethodField = "some value"; analyzeString(writeInSameMethodField); } @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*|^null$)") - @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void fieldWithNoWriteTest() { analyzeString(noWriteField); } - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void nonSupportedFieldTypeRead() { analyzeString(unsupportedTypeField.toString()); } - @Dynamic(n = 0, value = ".*") - @Dynamic(n = 1, value = ".*") - @PartiallyConstant(n = 2, value = "value=.*") - @PartiallyConstant(n = 3, value = "value=.*.*") + @Dynamic(n = 0, levels = Level.TRUTH, value = ".*") + @Dynamic(n = 1, levels = Level.TRUTH, value = ".*") + @Failure(n = 1, levels = Level.L0) + @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "value=.*") + @Failure(n = 2, levels = Level.L0) + @PartiallyConstant(n = 3, levels = Level.TRUTH, value = "value=.*.*") + @Failure(n = 3, levels = Level.L0) public void parameterRead(String stringValue, StringBuilder sbValue) { analyzeString(stringValue); analyzeString(sbValue.toString()); @@ -126,9 +127,10 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { * Methods are called that return a string but are not within this project => cannot / will not interpret */ @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") - @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, value = ".*") + @Failure(n = 0, levels = Level.L0) + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = ".*") @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) - @Dynamic(n = 1, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.LOW, value = ".*") + @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = ".*") @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") public void methodsOutOfScopeTest() throws FileNotFoundException { File file = new File("my-file.txt"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java index ec2ec38b16..9402be3b88 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -14,23 +14,25 @@ public class FunctionCalls { */ public void analyzeString(String s) {} - // checks if a string value with append(s) is determined correctly - @Constant(n = 0, value = "java.lang.String") - @Constant(n = 1, value = "java.lang.Object") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "java.lang.Object") + @Failure(n = 1, levels = Level.L0) public void simpleStringConcatWithStaticFunctionCalls() { analyzeString(StringProvider.concat("java.lang.", "String")); analyzeString(StringProvider.concat("java.", StringProvider.concat("lang.", "Object"))); } @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.StringBuilder") - @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void fromFunctionCall() { analyzeString(getStringBuilderClassName()); } @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.StringBuilder") - @Invalid(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW) - @PartiallyConstant(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "java.lang..*") + @Failure(n = 0, levels = Level.L0) + @Invalid(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW) + @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "java.lang..*") public void fromConstantAndFunctionCall() { String className = "java.lang."; System.out.println(className); @@ -38,7 +40,8 @@ public void fromConstantAndFunctionCall() { analyzeString(className); } - @Constant(n = 0, value = "java.lang.Integer") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.Integer") + @Failure(n = 0, levels = Level.L0) public void fromStaticMethodWithParamTest() { analyzeString(StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer")); } @@ -53,17 +56,18 @@ public static String noReturnFunction() { throw new RuntimeException(); } - @Constant(n = 0, value = "Hello, World!") + @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World!") + @Failure(n = 0, levels = Level.L0) @Constant(n = 1, levels = Level.TRUTH, value = "Hello, World?") - @Failure(n = 1, levels = Level.L0) + @Failure(n = 1, levels = { Level.L0, Level.L1 }) public void functionWithFunctionParameter() { analyzeString(addExclamationMark(getHelloWorld())); analyzeString(addQuestionMark(getHelloWorld())); } - @Constant(n = 0, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") - @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "ERROR") - @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") + @Constant(n = 0, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.LOW, value = "ERROR") + @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") public void simpleNonVirtualFunctionCallTestWithIf(int i) { String s; if (i == 0) { @@ -77,8 +81,9 @@ public void simpleNonVirtualFunctionCallTestWithIf(int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") - @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "ERROR") - @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "ERROR") + @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") public void initFromNonVirtualFunctionCallTest(int i) { String s; if (i == 0) { @@ -92,7 +97,8 @@ public void initFromNonVirtualFunctionCallTest(int i) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "It is (great|Hello, World)") + @Constant(n = 0, levels = Level.TRUTH, value = "It is (great|Hello, World)") + @Failure(n = 0, levels = Level.L0) public void appendWithTwoDefSitesWithFuncCallTest(int i) { String s; if (i > 0) { @@ -107,15 +113,15 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { * A case where the single valid return value of the called function can be resolved without calling the function. */ @Constant(n = 0, levels = Level.TRUTH, domains = DomainLevel.L1, value = "(java.lang.Object|One|val)") - @Failure(n = 0, levels = Level.L0, domains = DomainLevel.L1) + @Failure(n = 0, levels = { Level.L0, Level.L1 }, domains = DomainLevel.L1) // Since the virtual function return value is inlined in L2 and its actual runtime return // value is not used, the function call gets converted to a method call, which modifies the // TAC: The def PC from the `analyzeString` parameter is now different and points to the def // PC for the `resolvableReturnValueFunction` parameter. This results in no string flow being // detected since the def and use sites are now inconsistent. // The actual truth @Constant(n = 0, value = "val", domains = DomainLevel.L2) - @Invalid(n = 0, levels = Level.L0, domains = DomainLevel.L2) @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2) + @Invalid(n = 0, levels = Level.L2, domains = DomainLevel.L2) public void resolvableReturnValue() { analyzeString(resolvableReturnValueFunction("val", 42)); } @@ -132,7 +138,7 @@ private String resolvableReturnValueFunction(String s, int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(One|val|java.lang.Object)") - @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void severalReturnValuesTest1() { analyzeString(severalReturnValuesWithSwitchFunction("val", 42)); } @@ -146,7 +152,8 @@ private String severalReturnValuesWithSwitchFunction(String s, int i) { } } - @Constant(n = 0, value = "(that's odd|Hello, World)") + @Constant(n = 0, levels = Level.TRUTH, value = "(that's odd|Hello, World)") + @Failure(n = 0, levels = Level.L0) public void severalReturnValuesTest2() { analyzeString(severalReturnValuesWithIfElseFunction(42)); } @@ -161,14 +168,15 @@ private static String severalReturnValuesWithIfElseFunction(int i) { } } - @Constant(n = 0, value = "(Hello, World|my.helper.Class)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello, World|my.helper.Class)") + @Failure(n = 0, levels = Level.L0) public String calleeWithFunctionParameter(String s, float i) { analyzeString(s); return s; } @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World") - @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void firstCallerForCalleeWithFunctionParameter() { String s = calleeWithFunctionParameter(getHelloWorldProxy(), 900); analyzeString(s); @@ -178,7 +186,8 @@ public void secondCallerForCalleeWithFunctionParameter() { calleeWithFunctionParameter(getHelperClassProxy(), 900); } - @Constant(n = 0, value = "(Hello, World|my.helper.Class)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello, World|my.helper.Class)") + @Failure(n = 0, levels = Level.L0) public String calleeWithFunctionParameterMultipleCallsInSameMethodTest(String s, float i) { analyzeString(s); return s; @@ -189,6 +198,17 @@ public void callerForCalleeWithFunctionParameterMultipleCallsInSameMethodTest() calleeWithFunctionParameterMultipleCallsInSameMethodTest(getHelperClassProxy(), 900); } + @Constant(n = 0, levels = Level.TRUTH, value = "(string.1|string.2)") + public String calleeWithStringParameterMultipleCallsInSameMethodTest(String s, float i) { + analyzeString(s); + return s; + } + + public void callerForCalleeWithStringParameterMultipleCallsInSameMethodTest() { + calleeWithStringParameterMultipleCallsInSameMethodTest("string.1", 900); + calleeWithStringParameterMultipleCallsInSameMethodTest("string.2", 900); + } + public static String getHelloWorldProxy() { return getHelloWorld(); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java index f5cd63fbab..fbcd243212 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java @@ -15,28 +15,29 @@ public class Integration { */ public void analyzeString(String s) {} - @Constant(n = 0, value = "java.lang.String") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") public void noCallersInformationRequiredTest(String s) { System.out.println(s); analyzeString("java.lang.String"); } - @Constant(n = 0, value = "some.test.value") + @Constant(n = 0, levels = Level.TRUTH, value = "some.test.value") + @Failure(n = 0, levels = Level.L0) public void systemPropertiesIntegrationTest() { System.setProperty("some.test.property", "some.test.value"); String s = System.getProperty("some.test.property"); analyzeString(s); } - @Constant(n = 0, value = "Hello World") - @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.TRUTH, value = "Hello World") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void knownHierarchyInstanceTest() { GreetingService gs = new HelloGreeting(); analyzeString(gs.getGreeting("World")); } @Constant(n = 0, levels = Level.TRUTH, value = "(Hello World|Hello)") - @Failure(n = 0, levels = Level.L0) + @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java index 430a7a7fc4..e695c23b35 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -20,10 +20,10 @@ public void analyzeString(String s) {} /** * Simple for loops with known and unknown bounds. Note that no analysis supports loops yet. */ - @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) - @PartiallyConstant(n = 1, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 1, value = ".*", levels = { Level.L0, Level.L1 }) + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") + @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "a(b)*") + @Dynamic(n = 1, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); for (int i = 0; i < 10; i++) { @@ -39,8 +39,9 @@ public void simpleForLoopWithKnownBounds() { analyzeString(sb.toString()); } - @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) - @Dynamic(n = 0, value = "(.*|.*yz)", levels = { Level.L0, Level.L1 }) + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "((x|^-?\\d+$))*yz") + @Dynamic(n = 0, levels = Level.L0, value = ".*") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = "(.*|.*yz)") public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 20; i++) { @@ -55,8 +56,8 @@ public void ifElseInLoopWithAppendAfterwards() { analyzeString(sb.toString()); } - @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") + @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") public void nestedLoops(int range) { for (int i = 0; i < range; i++) { StringBuilder sb = new StringBuilder("a"); @@ -68,7 +69,8 @@ public void nestedLoops(int range) { } @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) - @Dynamic(n = 0, value = "(.*|.*yz)", levels = { Level.L0, Level.L1 }) + @Dynamic(n = 0, levels = Level.L0, value = ".*") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = "(.*|.*yz)") public void stringBufferExample() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 20; i++) { @@ -84,7 +86,7 @@ public void stringBufferExample() { } @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) public void whileTrueWithBreak() { StringBuilder sb = new StringBuilder("a"); while (true) { @@ -97,7 +99,7 @@ public void whileTrueWithBreak() { } @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) public void whileNonTrueWithBreak(int i) { StringBuilder sb = new StringBuilder("a"); int j = 0; @@ -112,16 +114,18 @@ public void whileNonTrueWithBreak(int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(iv1|iv2): ") + @Dynamic(n = 0, levels = Level.L0, value = ".*") // The real value is not fully resolved yet, since the string builder is used in a while loop, // which leads to the string builder potentially carrying any value. This can be refined by // recording pc specific states during data flow analysis. - @Dynamic(n = 0, levels = { Level.L0 , Level.L1 }, soundness = SoundnessMode.LOW, value = "((iv1|iv2): |.*)") - @Dynamic(n = 0, levels = { Level.L0 , Level.L1 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "((iv1|iv2): |.*)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?") - @Dynamic(n = 1, levels = Level.L0, soundness = SoundnessMode.LOW, value = ".*") - @Dynamic(n = 1, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|.*.*)") - @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(.*|.*java.lang.Runtime)") - @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") + @Dynamic(n = 1, levels = Level.L0, value = ".*") + @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = ".*") + @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|.*.*)") + @Dynamic(n = 1, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(.*|.*java.lang.Runtime)") + @Dynamic(n = 1, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") public void extensiveWithManyControlStructures(boolean cond) { StringBuilder sb = new StringBuilder(); if (cond) { @@ -150,11 +154,12 @@ public void extensiveWithManyControlStructures(boolean cond) { // The bytecode produces an "if" within an "if" inside the first loop => two conditions @Constant(n = 0, levels = Level.TRUTH, value = "abc((d)?)*") - @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, value = ".*") + @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") @Constant(n = 1, levels = Level.TRUTH, value = "") - @Dynamic(n = 1, levels = { Level.L0, Level.L1 }, value = "(.*|)") + @Dynamic(n = 1, levels = Level.L0, value = ".*") + @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, value = "(.*|)") @Dynamic(n = 2, levels = Level.TRUTH, value = "((.*)?)*") - @Dynamic(n = 2, levels = { Level.L0, Level.L1 }, value = ".*") + @Dynamic(n = 2, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") public void breakContinueExamples(int value) { StringBuilder sb1 = new StringBuilder("abc"); for (int i = 0; i < value; i++) { @@ -195,7 +200,7 @@ public void breakContinueExamples(int value) { * Some comprehensive example for experimental purposes taken from the JDK and slightly modified */ @Constant(n = 0, value = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1 }) + @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) protected void setDebugFlags(String[] var1) { for(int var2 = 0; var2 < var1.length; ++var2) { String var3 = var1[var2]; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java index 88fd27ed7c..746ca02eb2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java @@ -15,8 +15,10 @@ public class SimpleControlStructures { */ public void analyzeString(String s) {} - @Dynamic(n = 0, value = "(x|^-?\\d+$)") - @Constant(n = 1, value = "(42-42|x)") + @Dynamic(n = 0, levels = Level.TRUTH, value = "(x|^-?\\d+$)") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "(42-42|x)") + @Failure(n = 1, levels = Level.L0) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); @@ -33,7 +35,8 @@ public void ifElseWithStringBuilderWithIntExpr() { analyzeString(sb2.toString()); } - @PartiallyConstant(n = 0, value = "(3.142.71828|^-?\\d*\\.{0,1}\\d+$2.71828)") + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(3.142.71828|^-?\\d*\\.{0,1}\\d+$2.71828)") + @Failure(n = 0, levels = Level.L0) public void ifElseWithStringBuilderWithFloatExpr() { StringBuilder sb1 = new StringBuilder(); int i = new Random().nextInt(); @@ -47,8 +50,10 @@ public void ifElseWithStringBuilderWithFloatExpr() { analyzeString(sb1.toString()); } - @Constant(n = 0, value = "(a|b)") - @Constant(n = 1, value = "(ab|ac)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|b)") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "(ab|ac)") + @Failure(n = 1, levels = Level.L0) public void ifElseWithStringBuilder() { StringBuilder sb1; StringBuilder sb2 = new StringBuilder("a"); @@ -65,7 +70,8 @@ public void ifElseWithStringBuilder() { analyzeString(sb2.toString()); } - @Constant(n = 0, value = "(abcd|axyz)") + @Constant(n = 0, levels = Level.TRUTH, value = "(abcd|axyz)") + @Failure(n = 0, levels = Level.L0) public void ifElseWithStringBuilderWithMultipleAppends() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); @@ -81,7 +87,8 @@ public void ifElseWithStringBuilderWithMultipleAppends() { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(abcd|a|axyz)") + @Constant(n = 0, levels = Level.TRUTH, value = "(abcd|a|axyz)") + @Failure(n = 0, levels = Level.L0) public void ifElseWithStringBuilderWithMultipleAppendsAndNonUsedElseIf() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); @@ -99,7 +106,8 @@ public void ifElseWithStringBuilderWithMultipleAppendsAndNonUsedElseIf() { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(a|ab)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab)") + @Failure(n = 0, levels = Level.L0) public void ifWithoutElse() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); @@ -109,7 +117,8 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } - @Constant(n = 0, value = "java.lang.Runtime") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.Runtime") + @Failure(n = 0, levels = Level.L0) public void ifConditionAppendsToString(String className) { StringBuilder sb = new StringBuilder(); if (sb.append("java.lang.Runtime").toString().equals(className)) { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java index c955e7e503..fb43242513 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java @@ -18,16 +18,19 @@ public class SimpleStringBuilderOps { public void analyzeString(String s) {} public void analyzeString(StringBuilder sb) {} - @Constant(n = 0, value = "java.lang.String") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") + @Failure(n = 0, levels = Level.L0) public void multipleDirectAppends() { StringBuilder sb = new StringBuilder("java"); sb.append(".").append("lang").append(".").append("String"); analyzeString(sb.toString()); } - @Constant(n = 0, value = "SomeOther") - @Constant(n = 1, value = "SomeOther", levels = Level.TRUTH) - @Constant(n = 1, value = "(Some|SomeOther)", levels = { Level.L0, Level.L1 }) + @Constant(n = 0, levels = Level.TRUTH, value = "SomeOther") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "SomeOther") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 1, levels = { Level.L1, Level.L2 }, value = "(Some|SomeOther)") public void stringValueOfWithStringBuilder() { StringBuilder sb = new StringBuilder("Some"); sb.append("Other"); @@ -36,8 +39,10 @@ public void stringValueOfWithStringBuilder() { analyzeString(sb); } - @Constant(n = 0, value = "Some") - @Constant(n = 1, value = "Other") + @Constant(n = 0, levels = Level.TRUTH, value = "Some") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "Other") + @Failure(n = 1, levels = Level.L0) public void stringBuilderBufferInitArguments() { StringBuilder sb = new StringBuilder("Some"); analyzeString(sb.toString()); @@ -46,8 +51,10 @@ public void stringBuilderBufferInitArguments() { analyzeString(sb2.toString()); } - @Constant(n = 0, value = "java.lang.StringBuilder") - @Constant(n = 1, value = "java.lang.StringBuilder") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.StringBuilder") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "java.lang.StringBuilder") + @Failure(n = 1, levels = Level.L0) public void simpleClearExamples() { StringBuilder sb1 = new StringBuilder("init_value:"); sb1.setLength(0); @@ -62,7 +69,8 @@ public void simpleClearExamples() { analyzeString(sb2.toString()); } - @Constant(n = 0, value = "(Goodbye|init_value:Hello, world!Goodbye)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Goodbye|init_value:Hello, world!Goodbye)") + @Failure(n = 0, levels = Level.L0) public void advancedClearExampleWithSetLength(int value) { StringBuilder sb = new StringBuilder("init_value:"); if (value < 10) { @@ -74,8 +82,10 @@ public void advancedClearExampleWithSetLength(int value) { analyzeString(sb.toString()); } - @Dynamic(n = 0, value = ".*") - @PartiallyConstant(n = 1, value = "(.*Goodbye|init_value:Hello, world!Goodbye)") + @Dynamic(n = 0, levels = Level.TRUTH, value = ".*") + @Failure(n = 0, levels = Level.L0) + @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(.*Goodbye|init_value:Hello, world!Goodbye)") + @Failure(n = 1, levels = Level.L0) public void replaceExamples(int value) { StringBuilder sb1 = new StringBuilder("init_value"); sb1.replace(0, 5, "replaced_value"); @@ -91,8 +101,10 @@ public void replaceExamples(int value) { analyzeString(sb1.toString()); } - @Constant(n = 0, value = "B.") - @Constant(n = 1, value = "java.langStringB.") + @Constant(n = 0, levels = Level.TRUTH, value = "B.") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "java.langStringB.") + @Failure(n = 1, levels = Level.L0) public void directAppendConcatsWith2ndStringBuilder() { StringBuilder sb = new StringBuilder("java"); StringBuilder sb2 = new StringBuilder("B"); @@ -107,7 +119,8 @@ public void directAppendConcatsWith2ndStringBuilder() { /** * Checks if the value of a string builder that depends on the complex construction of a second one can be determined. */ - @Constant(n = 0, value = "java.lang.(Object|Runtime)") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.(Object|Runtime)") + @Failure(n = 0, levels = Level.L0) public void complexSecondStringBuilderRead(String className) { StringBuilder sbObj = new StringBuilder("Object"); StringBuilder sbRun = new StringBuilder("Runtime"); @@ -124,7 +137,8 @@ public void complexSecondStringBuilderRead(String className) { analyzeString(sb2.toString()); } - @Constant(n = 0, value = "(java.lang.Object|java.lang.Runtime)") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime)") + @Failure(n = 0, levels = Level.L0) public void simpleSecondStringBuilderRead(String className) { StringBuilder sbObj = new StringBuilder("Object"); StringBuilder sbRun = new StringBuilder("Runtime"); @@ -139,8 +153,10 @@ public void simpleSecondStringBuilderRead(String className) { analyzeString(sb1.toString()); } - @Constant(n = 0, value = "(Object|ObjectRuntime)") - @Constant(n = 1, value = "(RuntimeObject|Runtime)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Object|ObjectRuntime)") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "(RuntimeObject|Runtime)") + @Failure(n = 1, levels = Level.L0) public void crissCrossExample(String className) { StringBuilder sbObj = new StringBuilder("Object"); StringBuilder sbRun = new StringBuilder("Runtime"); @@ -155,7 +171,8 @@ public void crissCrossExample(String className) { analyzeString(sbRun.toString()); } - @PartiallyConstant(n = 0, value = "File Content:.*", soundness = SoundnessMode.HIGH) + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "File Content:.*", soundness = SoundnessMode.HIGH) + @Failure(n = 0, levels = Level.L0) public void withUnknownAppendSource(String filename) throws IOException { StringBuilder sb = new StringBuilder("File Content:"); String data = new String(Files.readAllBytes(Paths.get(filename))); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java index 802b2a0ffd..bf8ba123b0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java @@ -40,9 +40,8 @@ public class SimpleStringOps { */ public void analyzeString(String s) {} - // read-only string variable, trivial case - @Constant(n = 0, value = "java.lang.String") - @Constant(n = 1, value = "java.lang.String") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") + @Constant(n = 1, levels = Level.TRUTH, value = "java.lang.String") public void constantStringReads() { analyzeString("java.lang.String"); @@ -50,19 +49,22 @@ public void constantStringReads() { analyzeString(className); } - @Constant(n = 0, value = "c") - @Constant(n = 1, value = "42.3") + @Constant(n = 0, levels = Level.TRUTH, value = "c") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "42.3") + @Failure(n = 1, levels = Level.L0) @Constant(n = 2, levels = Level.TRUTH, value = "java.lang.Runtime") - @Failure(n = 2, levels = Level.L0) + @Failure(n = 2, levels = { Level.L0, Level.L1 }) public void valueOfTest() { analyzeString(String.valueOf('c')); analyzeString(String.valueOf((float) 42.3)); analyzeString(String.valueOf(getRuntimeClassName())); } - // checks if a string value with append(s) is determined correctly - @Constant(n = 0, value = "java.lang.String") - @Constant(n = 1, value = "java.lang.Object") + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "java.lang.Object") + @Failure(n = 1, levels = Level.L0) public void simpleStringConcat() { String className1 = "java.lang."; System.out.println(className1); @@ -77,16 +79,17 @@ public void simpleStringConcat() { analyzeString(className2); } - // checks if the substring of a constant string value is determined correctly - @Constant(n = 0, value = "va.") - @Constant(n = 1, value = "va.lang.") + @Constant(n = 0, levels = Level.TRUTH, value = "va.") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, value = "va.lang.") + @Failure(n = 1, levels = Level.L0) public void simpleSubstring() { String someString = "java.lang."; analyzeString(someString.substring(2, 5)); analyzeString(someString.substring(2)); } - @Constant(n = 0, value = "(java.lang.System|java.lang.Runtime)") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.System|java.lang.Runtime)") public void multipleConstantDefSites(boolean cond) { String s; if (cond) { @@ -97,7 +100,8 @@ public void multipleConstantDefSites(boolean cond) { analyzeString(s); } - @Constant(n = 0, value = "It is (great|not great)") + @Constant(n = 0, levels = Level.TRUTH, value = "It is (great|not great)") + @Failure(n = 0, levels = Level.L0) public void appendWithTwoDefSites(int i) { String s; if (i > 0) { @@ -108,9 +112,12 @@ public void appendWithTwoDefSites(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } - @Constant(n = 0, value = "(Some|SomeOther)") - @Dynamic(n = 1, value = "(.*|Some)") - @PartiallyConstant(n = 2, value = "(SomeOther|Some.*)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Some|SomeOther)") + @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "Some") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(Some|.*)") + @Dynamic(n = 1, levels = Level.TRUTH, value = "(.*|Some)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "(SomeOther|Some.*)") + @Failure(n = 2, levels = Level.L0) public void ternaryOperators(boolean flag, String param) { String s1 = "Some"; String s2 = s1 + "Other"; @@ -120,7 +127,8 @@ public void ternaryOperators(boolean flag, String param) { analyzeString(flag ? s1 + param : s2); } - @Constant(n = 0, value = "(a|ab|ac)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") + @Failure(n = 0, levels = Level.L0) public void switchRelevantAndIrrelevant(int value) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -138,7 +146,8 @@ public void switchRelevantAndIrrelevant(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(ab|ac|a|ad)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|a|ad)") + @Failure(n = 0, levels = Level.L0) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -159,7 +168,8 @@ public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(a|ab|ac)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") + @Failure(n = 0, levels = Level.L0) public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -179,7 +189,8 @@ public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(ab|ac|ad)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad)") + @Failure(n = 0, levels = Level.L0) public void switchRelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -196,7 +207,8 @@ public void switchRelevantWithRelevantDefault(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(ab|ac|a|ad|af)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|a|ad|af)") + @Failure(n = 0, levels = Level.L0) public void switchNestedNoNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -220,7 +232,8 @@ public void switchNestedNoNestedDefault(int value, int value2) { analyzeString(sb.toString()); } - @Constant(n = 0, value = "(ab|ac|ad|ae|af)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad|ae|af)") + @Failure(n = 0, levels = Level.L0) public void switchNestedWithNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -253,9 +266,11 @@ public void switchNestedWithNestedDefault(int value, int value2) { */ @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.System|java.lang.StringBuilder)") @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "java.lang.System") - @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang..*)") - @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") - @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System)") + @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "java.lang.System") + @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang..*)") + @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") + @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java index 6ee7e130ed..6288f3a593 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java @@ -21,7 +21,7 @@ */ String value(); - Level[] levels() default { Level.TRUTH }; + Level[] levels(); DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java index 7429f2b586..a6bf417c0b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java @@ -21,7 +21,7 @@ */ String value(); - Level[] levels() default { Level.TRUTH }; + Level[] levels(); DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java index a76651ac30..d366336416 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java @@ -1,10 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.properties.string_analysis; -import org.opalj.fpcf.properties.PropertyValidator; - import java.lang.annotation.*; +/** + * Note that this annotation will be rewritten into {@link Invalid} or {@link Dynamic} depending on the soundness mode. + */ @Documented @Repeatable(Failures.class) @Retention(RetentionPolicy.CLASS) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java index 43bb2c7c57..de0f6da434 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java @@ -16,7 +16,7 @@ String reason() default "N/A"; - Level[] levels() default { Level.TRUTH }; + Level[] levels(); DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java index 99477f8120..2d05ba6526 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java @@ -8,7 +8,8 @@ public enum Level { TRUTH("TRUTH"), L0("L0"), - L1("L1"); + L1("L1"), + L2("L2"); private final String value; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java index b8a64f9138..8ac14772a7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java @@ -21,7 +21,7 @@ */ String value(); - Level[] levels() default { Level.TRUTH }; + Level[] levels(); DomainLevel[] domains() default { DomainLevel.L1, DomainLevel.L2 }; 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 e0f928f8ea..31bb0a2890 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 @@ -50,6 +50,7 @@ import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis import org.opalj.tac.fpcf.analyses.systemproperties.EagerSystemPropertiesAnalysisScheduler sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -92,8 +93,8 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { def initBeforeCallGraph(p: Project[URL]): Unit = {} - def runTests(): Unit = { - describe(s"the string analysis on level $level is started") { + describe(s"using level=$level, domainLevel=$domainLevel, soundness=$soundnessMode") { + describe(s"the string analysis is started") { var entities = Iterable.empty[(VariableContext, Method)] val as = executeAnalyses( analyses, @@ -251,34 +252,26 @@ sealed abstract class L0StringAnalysisTest extends StringAnalysisTest { override final def level = Level.L0 - override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { - LazyL0StringAnalysis.allRequiredAnalyses :+ - EagerSystemPropertiesAnalysisScheduler - } + override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = + LazyL0StringAnalysis.allRequiredAnalyses } class L0StringAnalysisWithL1DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - - describe("using the l1 default domain") { runTests() } } class L0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - - describe("using the l2 default domain") { runTests() } } class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH - - describe("using the l2 default domain and the high soundness mode") { runTests() } } /** @@ -289,10 +282,44 @@ class HighSoundnessL0StringAnalysisWithL2DefaultDomainTest extends L0StringAnaly */ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { - override def level = Level.L1 + override final def level = Level.L1 override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL1StringAnalysis.allRequiredAnalyses :+ + EagerSystemPropertiesAnalysisScheduler + } +} + +class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L1 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW +} + +class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW +} + +class HighSoundnessL1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.HIGH +} + +/** + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis]] works correctly with + * respect to some well-defined tests. + * + * @author Maximilian Rüsch + */ +sealed abstract class L2StringAnalysisTest extends StringAnalysisTest { + + override def level = Level.L2 + + override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { + LazyL2StringAnalysis.allRequiredAnalyses :+ EagerFieldAccessInformationAnalysis :+ EagerSystemPropertiesAnalysisScheduler } @@ -305,26 +332,20 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { } } -class L1StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { +class L2StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - - describe("using the l1 default domain") { runTests() } } -class L1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { +class L2StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW - - describe("using the l2 default domain") { runTests() } } -class HighSoundnessL1StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { +class HighSoundnessL2StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH - - describe("using the l2 default domain and the high soundness mode") { runTests() } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index b16389ef03..1b3f2335e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -10,6 +10,7 @@ import scala.collection.mutable import org.opalj.br.DefinedMethod import org.opalj.br.Method +import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis @@ -60,14 +61,21 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.values.flatMap { v => if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs - }.foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => - val mappedWebs = reducedWebs.map(w => (w, w.identifiesSameVarAs(web))) - if (!mappedWebs.exists(_._2)) { - reducedWebs :+ web - } else { - mappedWebs.filterNot(_._2).map(_._1) :+ mappedWebs.filter(_._2).map(_._1).reduce(_.combine(_)).combine(web) - } - }.iterator + } + .toSeq.appendedAll(tac.params.parameters.zipWithIndex.map { + case (param, index) => + PDUWeb(IntTrieSet(-index - 1), if (param != null) param.useSites else IntTrieSet.empty) + }) + .foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => + val mappedWebs = reducedWebs.map(w => (w, w.identifiesSameVarAs(web))) + if (!mappedWebs.exists(_._2)) { + reducedWebs :+ web + } else { + mappedWebs.filterNot(_._2).map(_._1) :+ mappedWebs.filter(_._2).map(_._1).reduce(_.combine(_)).combine( + web + ) + } + }.iterator } case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index 7cfde1ea06..88b6b23d7d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -4,6 +4,7 @@ package tac package fpcf package analyses package string +package l0 package interpretation import org.opalj.br.ComputationalTypeFloat diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index f70a85833f..d3455b1383 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -9,9 +9,7 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** @@ -28,34 +26,17 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) - // Currently unsupported - case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) - case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.failure(target) + case ExprStmt(_, expr: VirtualFunctionCall[V]) => StringInterpreter.failure(expr.receiver.asVar) + case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => StringInterpreter.failure(expr.receiver.asVar) - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - - case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => - new L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => - new L0VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - - case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) - - case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case vmc: VirtualMethodCall[V] => L0VirtualMethodCallInterpreter.interpret(vmc) - case nvmc: NonVirtualMethodCall[V] => L0NonVirtualMethodCallInterpreter.interpret(nvmc) + case vmc: VirtualMethodCall[V] => StringInterpreter.failure(vmc.receiver.asVar) + case nvmc: NonVirtualMethodCall[V] => StringInterpreter.failure(nvmc.receiver.asVar) - case Assignment(_, target, _) => - StringInterpreter.failure(target) + case Assignment(_, target, _) => StringInterpreter.failure(target) case ReturnValue(pc, expr) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala deleted file mode 100644 index 75da830772..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualFunctionCallInterpreter.scala +++ /dev/null @@ -1,217 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string -package l0 -package interpretation - -import org.opalj.br.ComputationalTypeDouble -import org.opalj.br.ComputationalTypeFloat -import org.opalj.br.ComputationalTypeInt -import org.opalj.br.DoubleType -import org.opalj.br.FloatType -import org.opalj.br.IntLikeType -import org.opalj.br.ObjectType -import org.opalj.br.fpcf.properties.string.StringConstancyLevel -import org.opalj.br.fpcf.properties.string.StringTreeConcat -import org.opalj.br.fpcf.properties.string.StringTreeConst -import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat -import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt -import org.opalj.br.fpcf.properties.string.StringTreeNode -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode -import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty -import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment -import org.opalj.value.TheIntegerValue - -/** - * Responsible for processing [[VirtualFunctionCall]]s without a call graph. - * - * @author Maximilian Rüsch - */ -class L0VirtualFunctionCallInterpreter( - implicit val soundnessMode: SoundnessMode -) extends AssignmentLikeBasedStringInterpreter - with L0ArbitraryVirtualFunctionCallInterpreter - with L0AppendCallInterpreter - with L0SubstringCallInterpreter { - - override type T = AssignmentLikeStmt[V] - override type E = VirtualFunctionCall[V] - - override def interpretExpr(instr: T, call: E)(implicit - state: InterpretationState - ): ProperPropertyComputationResult = { - val at = Option.unless(!instr.isAssignment)(instr.asAssignment.targetVar.asVar.toPersistentForm(state.tac.stmts)) - val pt = call.receiver.asVar.toPersistentForm(state.tac.stmts) - - call.name match { - case "append" => interpretAppendCall(at, pt, call) - case "toString" => interpretToStringCall(at, pt) - case "replace" => interpretReplaceCall(pt) - case "substring" if call.descriptor.returnType == ObjectType.String => - interpretSubstringCall(at, pt, call) - case _ => - call.descriptor.returnType match { - case _: IntLikeType if at.isDefined => - computeFinalResult(StringFlowFunctionProperty.constForVariableAt( - state.pc, - at.get, - StringTreeDynamicInt - )) - case FloatType | DoubleType if at.isDefined => - computeFinalResult(StringFlowFunctionProperty.constForVariableAt( - state.pc, - at.get, - StringTreeDynamicFloat - )) - case _ if at.isDefined => - interpretArbitraryCall(at.get, call) - case _ => - computeFinalResult(StringFlowFunctionProperty.identity) - } - } - } - - private def interpretToStringCall(at: Option[PV], pt: PV)(implicit - state: InterpretationState - ): ProperPropertyComputationResult = { - if (at.isDefined) { - computeFinalResult( - Set(PDUWeb(state.pc, at.get), PDUWeb(state.pc, pt)), - (env: StringTreeEnvironment) => env.update(state.pc, at.get, env(state.pc, pt)) - ) - } else { - computeFinalResult(StringFlowFunctionProperty.identity) - } - } - - /** - * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. - */ - private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = { - // Improve: Support fluent API by returning combined web for both assignment target and call target - computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) - } -} - -private[string] trait L0ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { - - implicit val soundnessMode: SoundnessMode - - protected def interpretArbitraryCall(target: PV, call: E)(implicit - state: InterpretationState - ): ProperPropertyComputationResult = failure(target) -} - -/** - * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. - */ -private[string] trait L0AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { - - override type E = VirtualFunctionCall[V] - - def interpretAppendCall(at: Option[PV], pt: PV, call: E)(implicit - state: InterpretationState - ): ProperPropertyComputationResult = { - // .head because we want to evaluate only the first argument of append - val paramVar = call.params.head.asVar.toPersistentForm(state.tac.stmts) - - val ptWeb = PDUWeb(state.pc, pt) - val combinedWeb = if (at.isDefined) ptWeb.combine(PDUWeb(state.pc, at.get)) else ptWeb - - computeFinalResult( - Set(PDUWeb(state.pc, paramVar), combinedWeb), - (env: StringTreeEnvironment) => { - val valueState = env(state.pc, paramVar) - - val transformedValueState = paramVar.value.computationalType match { - case ComputationalTypeInt => - if (call.descriptor.parameterType(0).isCharType && valueState.isInstanceOf[StringTreeConst]) { - StringTreeConst(valueState.asInstanceOf[StringTreeConst].string.toInt.toChar.toString) - } else { - valueState - } - case ComputationalTypeFloat | ComputationalTypeDouble => - if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { - valueState - } else { - StringTreeDynamicFloat - } - case _ => - valueState - } - - env.update(combinedWeb, StringTreeConcat.fromNodes(env(state.pc, pt), transformedValueState)) - } - ) - } -} - -/** - * Interprets calls to [[String#substring]]. - */ -private[string] trait L0SubstringCallInterpreter extends AssignmentLikeBasedStringInterpreter { - - override type E <: VirtualFunctionCall[V] - - def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit - state: InterpretationState - ): ProperPropertyComputationResult = { - if (at.isEmpty) { - return computeFinalResult(StringFlowFunctionProperty.identity); - } - - val parameterCount = call.params.size - parameterCount match { - case 1 => - call.params.head.asVar.value match { - case TheIntegerValue(intVal) => - computeFinalResult( - Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), - (env: StringTreeEnvironment) => { - env(state.pc, pt) match { - case StringTreeConst(string) if intVal <= string.length => - env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) - case _ => - env.update(state.pc, at.get, StringTreeNode.lb) - } - } - ) - case _ => - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) - } - - case 2 => - (call.params.head.asVar.value, call.params(1).asVar.value) match { - case (TheIntegerValue(firstIntVal), TheIntegerValue(secondIntVal)) => - computeFinalResult( - Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), - (env: StringTreeEnvironment) => { - env(state.pc, pt) match { - case StringTreeConst(string) - if firstIntVal <= string.length - && secondIntVal <= string.length - && firstIntVal <= secondIntVal => - env.update( - state.pc, - at.get, - StringTreeConst(string.substring(firstIntVal, secondIntVal)) - ) - case _ => - env.update(state.pc, at.get, StringTreeNode.lb) - } - } - ) - case _ => - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) - } - - case _ => throw new IllegalStateException( - s"Unexpected parameter count for ${call.descriptor.toJava}. Expected one or two, got $parameterCount" - ) - } - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala index 0262c43b00..e7080ca284 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala @@ -4,6 +4,7 @@ package tac package fpcf package analyses package string +package l0 package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 10e6d6cf2b..0216e74060 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -6,24 +6,14 @@ package analyses package string package l1 -import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.br.fpcf.properties.SystemProperties -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation -import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHandler /** * @author Maximilian Rüsch */ -object L1StringAnalysis { - - private[l1] final val ConfigLogCategory = "analysis configuration - l1 string analysis" -} - object LazyL1StringAnalysis { def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( @@ -35,14 +25,5 @@ object LazyL1StringAnalysis { object LazyL1StringFlowAnalysis extends LazyStringFlowAnalysis { - override final def uses: Set[PropertyBounds] = super.uses ++ PropertyBounds.ubs( - Callees, - FieldWriteAccessInformation, - SystemProperties - ) - - override final def init(p: SomeProject, ps: PropertyStore): InitializationData = L1InterpretationHandler(p) - - override def requiredProjectInformation: ProjectInformationKeys = super.requiredProjectInformation ++ - L1InterpretationHandler.requiredProjectInformation + override def init(p: SomeProject, ps: PropertyStore): InitializationData = L1InterpretationHandler(p) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 98b4f80a8e..387de25aa8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.Method @@ -27,7 +27,7 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch */ -trait L0FunctionCallInterpreter +trait L1FunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter with ParameterEvaluatingStringInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 8fad74cd5e..275e15a96d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -7,20 +7,11 @@ package string package l1 package interpretation -import org.opalj.br.analyses.DeclaredFields -import org.opalj.br.analyses.DeclaredFieldsKey -import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.ContextProviderKey -import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.analyses.string.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.interpretation.SimpleValueConstExprInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualFunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0NonVirtualMethodCallInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0StaticFunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** @@ -30,56 +21,38 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class L1InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { - implicit val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) - implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) - override protected def processStatement(implicit state: InterpretationState ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) + case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.failure(target) - case stmt @ Assignment(_, _, expr: FieldRead[V]) => - new L1FieldReadInterpreter().interpretExpr(stmt, expr) - // Field reads without result usage are irrelevant - case ExprStmt(_, _: FieldRead[V]) => + case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case stmt: FieldWriteAccessStmt[V] => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( - stmt.pc, - stmt.value.asVar.toPersistentForm(state.tac.stmts) - )) - case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => new L1VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => - L0NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => - L0StaticFunctionCallInterpreter().interpretExpr(stmt, expr) + L1StaticFunctionCallInterpreter().interpretExpr(stmt, expr) // Static function calls without return value usage are irrelevant case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) - - case vmc: VirtualMethodCall[V] => - L0VirtualMethodCallInterpreter.interpret(vmc) - case nvmc: NonVirtualMethodCall[V] => - L0NonVirtualMethodCallInterpreter.interpret(nvmc) - - case Assignment(_, _, _: New) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case vmc: VirtualMethodCall[V] => L1VirtualMethodCallInterpreter.interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => L1NonVirtualMethodCallInterpreter.interpret(nvmc) case Assignment(_, target, _) => StringInterpreter.failure(target) @@ -97,7 +70,5 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend object L1InterpretationHandler { - def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, ContextProviderKey) - def apply(project: SomeProject): L1InterpretationHandler = new L1InterpretationHandler()(project) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index 7464112bd9..b28fe4163f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.analyses.SomeProject @@ -18,12 +18,12 @@ import org.opalj.tac.fpcf.properties.TACAI * * @author Maximilian Rüsch */ -case class L0NonVirtualFunctionCallInterpreter()( +case class L1NonVirtualFunctionCallInterpreter()( implicit val p: SomeProject, implicit val ps: PropertyStore, implicit val soundnessMode: SoundnessMode ) extends AssignmentLikeBasedStringInterpreter - with L0FunctionCallInterpreter { + with L1FunctionCallInterpreter { override type T = AssignmentLikeStmt[V] override type E = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 070b35423f..3e22bcc0d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst @@ -15,7 +15,7 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch */ -object L0NonVirtualMethodCallInterpreter extends StringInterpreter { +object L1NonVirtualMethodCallInterpreter extends StringInterpreter { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala similarity index 90% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 9cd3cc3424..f495e9d4c5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.ObjectType @@ -20,16 +20,16 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch */ -case class L0StaticFunctionCallInterpreter()( +case class L1StaticFunctionCallInterpreter()( implicit override val p: SomeProject, override val ps: PropertyStore, override val project: SomeProject, val soundnessMode: SoundnessMode ) extends AssignmentBasedStringInterpreter - with L0ArbitraryStaticFunctionCallInterpreter - with L0StringValueOfFunctionCallInterpreter - with L0SystemPropertiesInterpreter { + with L1ArbitraryStaticFunctionCallInterpreter + with L1StringValueOfFunctionCallInterpreter + with L1SystemPropertiesInterpreter { override type E = StaticFunctionCall[V] @@ -49,9 +49,9 @@ case class L0StaticFunctionCallInterpreter()( } } -private[string] trait L0ArbitraryStaticFunctionCallInterpreter +private[string] trait L1ArbitraryStaticFunctionCallInterpreter extends AssignmentBasedStringInterpreter - with L0FunctionCallInterpreter { + with L1FunctionCallInterpreter { implicit val p: SomeProject implicit val soundnessMode: SoundnessMode @@ -75,7 +75,7 @@ private[string] trait L0ArbitraryStaticFunctionCallInterpreter } } -private[string] trait L0StringValueOfFunctionCallInterpreter extends AssignmentBasedStringInterpreter { +private[string] trait L1StringValueOfFunctionCallInterpreter extends AssignmentBasedStringInterpreter { override type E <: StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala index 2a2bcc1167..13a7b9fa93 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0SystemPropertiesInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.analyses.SomeProject @@ -20,7 +20,7 @@ import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty -private[string] trait L0SystemPropertiesInterpreter extends StringInterpreter { +private[string] trait L1SystemPropertiesInterpreter extends StringInterpreter { implicit val ps: PropertyStore implicit val project: SomeProject diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 667f71484d..3799fe3968 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -7,130 +7,211 @@ package string package l1 package interpretation -import org.opalj.br.DefinedMethod -import org.opalj.br.Method +import org.opalj.br.ComputationalTypeDouble +import org.opalj.br.ComputationalTypeFloat +import org.opalj.br.ComputationalTypeInt +import org.opalj.br.DoubleType +import org.opalj.br.FloatType +import org.opalj.br.IntLikeType import org.opalj.br.ObjectType -import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.Context -import org.opalj.br.fpcf.properties.cg.Callees -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.InterimResult +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringTreeConcat +import org.opalj.br.fpcf.properties.string.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat +import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.SomeEOptionP -import org.opalj.fpcf.SomeEPS -import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0FunctionCallInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0SystemPropertiesInterpreter -import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0VirtualFunctionCallInterpreter -import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment +import org.opalj.value.TheIntegerValue /** - * Processes [[VirtualFunctionCall]]s similar to the [[L0VirtualFunctionCallInterpreter]] but handles arbitrary calls - * with a call graph. + * Responsible for processing [[VirtualFunctionCall]]s without a call graph. * * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter( - implicit val ps: PropertyStore, - implicit val contextProvider: ContextProvider, - implicit val project: SomeProject, - override implicit val soundnessMode: SoundnessMode -) extends L0VirtualFunctionCallInterpreter - with StringInterpreter - with L0SystemPropertiesInterpreter - with L1ArbitraryVirtualFunctionCallInterpreter { + implicit val soundnessMode: SoundnessMode +) extends AssignmentLikeBasedStringInterpreter + with L1ArbitraryVirtualFunctionCallInterpreter + with L1AppendCallInterpreter + with L1SubstringCallInterpreter { + override type T = AssignmentLikeStmt[V] override type E = VirtualFunctionCall[V] - override protected def interpretArbitraryCall(target: PV, call: E)( - implicit state: InterpretationState + override def interpretExpr(instr: T, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val at = Option.unless(!instr.isAssignment)(instr.asAssignment.targetVar.asVar.toPersistentForm(state.tac.stmts)) + val pt = call.receiver.asVar.toPersistentForm(state.tac.stmts) + + call.name match { + case "append" => interpretAppendCall(at, pt, call) + case "toString" => interpretToStringCall(at, pt) + case "replace" => interpretReplaceCall(pt) + case "substring" if call.descriptor.returnType == ObjectType.String => + interpretSubstringCall(at, pt, call) + case _ => + call.descriptor.returnType match { + case _: IntLikeType if at.isDefined => + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + at.get, + StringTreeDynamicInt + )) + case FloatType | DoubleType if at.isDefined => + computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + at.get, + StringTreeDynamicFloat + )) + case _ if at.isDefined => + interpretArbitraryCall(at.get, call) + case _ => + computeFinalResult(StringFlowFunctionProperty.identity) + } + } + } + + private def interpretToStringCall(at: Option[PV], pt: PV)(implicit + state: InterpretationState ): ProperPropertyComputationResult = { - if (call.name == "getProperty" && call.declaringClass == ObjectType("java/util/Properties")) { - interpretGetSystemPropertiesCall(target) + if (at.isDefined) { + computeFinalResult( + Set(PDUWeb(state.pc, at.get), PDUWeb(state.pc, pt)), + (env: StringTreeEnvironment) => env.update(state.pc, at.get, env(state.pc, pt)) + ) } else { - interpretArbitraryCallWithCallees(target) + computeFinalResult(StringFlowFunctionProperty.identity) } } + + /** + * Processes calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. + */ + private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = { + // Improve: Support fluent API by returning combined web for both assignment target and call target + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + } } -private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends L0FunctionCallInterpreter { +private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { - implicit val ps: PropertyStore - implicit val contextProvider: ContextProvider implicit val soundnessMode: SoundnessMode - override type CallState = CalleeDepender + protected def interpretArbitraryCall(target: PV, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = failure(target) +} + +/** + * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. + */ +private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { - protected[this] case class CalleeDepender( - override val target: PV, - override val parameters: Seq[PV], - methodContext: Context, - var calleeDependee: EOptionP[DefinedMethod, Callees] - ) extends FunctionCallState(target, parameters) { + override type E = VirtualFunctionCall[V] - override def hasDependees: Boolean = calleeDependee.isRefinable || super.hasDependees + def interpretAppendCall(at: Option[PV], pt: PV, call: E)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + // .head because we want to evaluate only the first argument of append + val paramVar = call.params.head.asVar.toPersistentForm(state.tac.stmts) + + val ptWeb = PDUWeb(state.pc, pt) + val combinedWeb = if (at.isDefined) ptWeb.combine(PDUWeb(state.pc, at.get)) else ptWeb + + computeFinalResult( + Set(PDUWeb(state.pc, paramVar), combinedWeb), + (env: StringTreeEnvironment) => { + val valueState = env(state.pc, paramVar) + + val transformedValueState = paramVar.value.computationalType match { + case ComputationalTypeInt => + if (call.descriptor.parameterType(0).isCharType && valueState.isInstanceOf[StringTreeConst]) { + StringTreeConst(valueState.asInstanceOf[StringTreeConst].string.toInt.toChar.toString) + } else { + valueState + } + case ComputationalTypeFloat | ComputationalTypeDouble => + if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { + valueState + } else { + StringTreeDynamicFloat + } + case _ => + valueState + } - override def dependees: Iterable[SomeEOptionP] = super.dependees ++ Seq(calleeDependee).filter(_.isRefinable) + env.update(combinedWeb, StringTreeConcat.fromNodes(env(state.pc, pt), transformedValueState)) + } + ) } +} + +/** + * Interprets calls to [[String#substring]]. + */ +private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStringInterpreter { - protected def interpretArbitraryCallWithCallees(target: PV)(implicit + override type E <: VirtualFunctionCall[V] + + def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) - val depender = CalleeDepender(target, params, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) - - if (depender.calleeDependee.isEPK) { - InterimResult.forUB( - InterpretationHandler.getEntity(state), - StringFlowFunctionProperty.ub, - Set(depender.calleeDependee), - continuation(state, depender) - ) - } else { - continuation(state, depender)(depender.calleeDependee.asInstanceOf[SomeEPS]) + if (at.isEmpty) { + return computeFinalResult(StringFlowFunctionProperty.identity); } - } - override protected[this] def continuation( - state: InterpretationState, - callState: CallState - )(eps: SomeEPS): ProperPropertyComputationResult = { - eps match { - case UBP(c: Callees) => - callState.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] - val newMethods = getNewMethodsFromCallees(callState.methodContext, c)(state, callState) - if (newMethods.isEmpty && eps.isFinal) { - // Improve add previous results back - failure(callState.target)(state, soundnessMode) - } else { - for { - method <- newMethods - } { - callState.addCalledMethod(method, ps(method, TACAI.key)) - } - - interpretArbitraryCallToFunctions(state, callState) + val parameterCount = call.params.size + parameterCount match { + case 1 => + call.params.head.asVar.value match { + case TheIntegerValue(intVal) => + computeFinalResult( + Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), + (env: StringTreeEnvironment) => { + env(state.pc, pt) match { + case StringTreeConst(string) if intVal <= string.length => + env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) + case _ => + env.update(state.pc, at.get, StringTreeNode.lb) + } + } + ) + case _ => + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) } - case _ => super.continuation(state, callState)(eps) - } - } + case 2 => + (call.params.head.asVar.value, call.params(1).asVar.value) match { + case (TheIntegerValue(firstIntVal), TheIntegerValue(secondIntVal)) => + computeFinalResult( + Set(PDUWeb(state.pc, pt), PDUWeb(state.pc, at.get)), + (env: StringTreeEnvironment) => { + env(state.pc, pt) match { + case StringTreeConst(string) + if firstIntVal <= string.length + && secondIntVal <= string.length + && firstIntVal <= secondIntVal => + env.update( + state.pc, + at.get, + StringTreeConst(string.substring(firstIntVal, secondIntVal)) + ) + case _ => + env.update(state.pc, at.get, StringTreeNode.lb) + } + } + ) + case _ => + computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) + } - private def getNewMethodsFromCallees(context: Context, callees: Callees)(implicit - state: InterpretationState, - callState: CallState - ): Seq[Method] = { - // IMPROVE only process newest callees - callees.callees(context, state.pc) - // IMPROVE add some uncertainty element if methods with unknown body exist - .filter(_.method.hasSingleDefinedMethod) - .map(_.method.definedMethod) - .filterNot(callState.calleeMethods.contains) - .distinct.toList.sortBy(_.classFile.fqn) + case _ => throw new IllegalStateException( + s"Unexpected parameter count for ${call.descriptor.toJava}. Expected one or two, got $parameterCount" + ) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 7e460ea25e..5130ba636d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l0 +package l1 package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst @@ -18,7 +18,7 @@ import org.opalj.value.TheIntegerValue /** * @author Maximilian Rüsch */ -object L0VirtualMethodCallInterpreter extends StringInterpreter { +object L1VirtualMethodCallInterpreter extends StringInterpreter { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala new file mode 100644 index 0000000000..377d2aec61 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala @@ -0,0 +1,48 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l2 + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.properties.SystemProperties +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.l2.interpretation.L2InterpretationHandler + +/** + * @author Maximilian Rüsch + */ +object L2StringAnalysis { + + private[l2] final val ConfigLogCategory = "analysis configuration - l1 string analysis" +} + +object LazyL2StringAnalysis { + + def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( + LazyStringAnalysis, + LazyMethodStringFlowAnalysis, + LazyL2StringFlowAnalysis + ) +} + +object LazyL2StringFlowAnalysis extends LazyStringFlowAnalysis { + + override final def uses: Set[PropertyBounds] = super.uses ++ PropertyBounds.ubs( + Callees, + FieldWriteAccessInformation, + SystemProperties + ) + + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = L2InterpretationHandler(p) + + override def requiredProjectInformation: ProjectInformationKeys = super.requiredProjectInformation ++ + L2InterpretationHandler.requiredProjectInformation +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index a2c298592a..6bdb104d40 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l1 +package l2 package interpretation import scala.collection.mutable.ListBuffer @@ -32,7 +32,7 @@ import org.opalj.log.LogContext import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode -import org.opalj.tac.fpcf.analyses.string.l1.L1StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l2.L2StringAnalysis import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** @@ -41,7 +41,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * * @author Maximilian Rüsch */ -class L1FieldReadInterpreter( +class L2FieldReadInterpreter( implicit val ps: PropertyStore, implicit val project: SomeProject, implicit val declaredFields: DeclaredFields, @@ -67,12 +67,12 @@ class L1FieldReadInterpreter( } catch { case t: Throwable => logOnce { - Error(L1StringAnalysis.ConfigLogCategory, s"couldn't read: $FieldWriteThresholdConfigKey", t) + Error(L2StringAnalysis.ConfigLogCategory, s"couldn't read: $FieldWriteThresholdConfigKey", t) } 10 } - logOnce(Info(L1StringAnalysis.ConfigLogCategory, "uses a field write threshold of " + threshold)) + logOnce(Info(L2StringAnalysis.ConfigLogCategory, "uses a field write threshold of " + threshold)) threshold } @@ -107,7 +107,7 @@ class L1FieldReadInterpreter( /** * Currently, fields are approximated using the following approach: If a field of a type not supported by the - * [[L1StringAnalysis]] is passed, a flow function producing the LB will be produced. Otherwise, all write accesses + * [[L2StringAnalysis]] is passed, a flow function producing the LB will be produced. 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 * [[org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC]]. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala new file mode 100644 index 0000000000..09e046292d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -0,0 +1,103 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l2 +package interpretation + +import org.opalj.br.analyses.DeclaredFields +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1StaticFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1VirtualMethodCallInterpreter +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * @inheritdoc + * + * @author Maximilian Rüsch + */ +class L2InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { + + implicit val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) + implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) + + override protected def processStatement(implicit + state: InterpretationState + ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + + // Currently unsupported + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) + + case stmt @ Assignment(_, _, expr: FieldRead[V]) => + new L2FieldReadInterpreter().interpretExpr(stmt, expr) + // Field reads without result usage are irrelevant + case ExprStmt(_, _: FieldRead[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case stmt: FieldWriteAccessStmt[V] => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + stmt.pc, + stmt.value.asVar.toPersistentForm(state.tac.stmts) + )) + + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + new L2VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + new L2VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => + L1StaticFunctionCallInterpreter().interpretExpr(stmt, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + + case vmc: VirtualMethodCall[V] => + L1VirtualMethodCallInterpreter.interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => + L1NonVirtualMethodCallInterpreter.interpret(nvmc) + + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case Assignment(_, target, _) => + StringInterpreter.failure(target) + + case ReturnValue(pc, expr) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + pc, + expr.asVar.toPersistentForm(state.tac.stmts) + )) + + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + } +} + +object L2InterpretationHandler { + + def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, ContextProviderKey) + + def apply(project: SomeProject): L2InterpretationHandler = new L2InterpretationHandler()(project) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..08a0128a09 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -0,0 +1,136 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l2 +package interpretation + +import org.opalj.br.DefinedMethod +import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1FunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1SystemPropertiesInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1VirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * Processes [[VirtualFunctionCall]]s similar to the [[L1VirtualFunctionCallInterpreter]] but handles arbitrary calls + * with a call graph. + * + * @author Maximilian Rüsch + */ +class L2VirtualFunctionCallInterpreter( + implicit val ps: PropertyStore, + implicit val contextProvider: ContextProvider, + implicit val project: SomeProject, + override implicit val soundnessMode: SoundnessMode +) extends L1VirtualFunctionCallInterpreter + with StringInterpreter + with L1SystemPropertiesInterpreter + with L2ArbitraryVirtualFunctionCallInterpreter { + + override type E = VirtualFunctionCall[V] + + override protected def interpretArbitraryCall(target: PV, call: E)( + implicit state: InterpretationState + ): ProperPropertyComputationResult = { + if (call.name == "getProperty" && call.declaringClass == ObjectType("java/util/Properties")) { + interpretGetSystemPropertiesCall(target) + } else { + interpretArbitraryCallWithCallees(target) + } + } +} + +private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1FunctionCallInterpreter { + + implicit val ps: PropertyStore + implicit val contextProvider: ContextProvider + implicit val soundnessMode: SoundnessMode + + override type CallState = CalleeDepender + + protected[this] case class CalleeDepender( + override val target: PV, + override val parameters: Seq[PV], + methodContext: Context, + var calleeDependee: EOptionP[DefinedMethod, Callees] + ) extends FunctionCallState(target, parameters) { + + override def hasDependees: Boolean = calleeDependee.isRefinable || super.hasDependees + + override def dependees: Iterable[SomeEOptionP] = super.dependees ++ Seq(calleeDependee).filter(_.isRefinable) + } + + protected def interpretArbitraryCallWithCallees(target: PV)(implicit + state: InterpretationState + ): ProperPropertyComputationResult = { + val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + val depender = CalleeDepender(target, params, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) + + if (depender.calleeDependee.isEPK) { + InterimResult.forUB( + InterpretationHandler.getEntity(state), + StringFlowFunctionProperty.ub, + Set(depender.calleeDependee), + continuation(state, depender) + ) + } else { + continuation(state, depender)(depender.calleeDependee.asInstanceOf[SomeEPS]) + } + } + + override protected[this] def continuation( + state: InterpretationState, + callState: CallState + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case UBP(c: Callees) => + callState.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] + val newMethods = getNewMethodsFromCallees(callState.methodContext, c)(state, callState) + if (newMethods.isEmpty && eps.isFinal) { + // Improve add previous results back + failure(callState.target)(state, soundnessMode) + } else { + for { + method <- newMethods + } { + callState.addCalledMethod(method, ps(method, TACAI.key)) + } + + interpretArbitraryCallToFunctions(state, callState) + } + + case _ => super.continuation(state, callState)(eps) + } + } + + private def getNewMethodsFromCallees(context: Context, callees: Callees)(implicit + state: InterpretationState, + callState: CallState + ): Seq[Method] = { + // IMPROVE only process newest callees + callees.callees(context, state.pc) + // IMPROVE add some uncertainty element if methods with unknown body exist + .filter(_.method.hasSingleDefinedMethod) + .map(_.method.definedMethod) + .filterNot(callState.calleeMethods.contains) + .distinct.toList.sortBy(_.classFile.fqn) + } +} From 221f262346ba311e69f7074a4f938fccc4c70d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 22:42:04 +0200 Subject: [PATCH 508/583] Reduce usage of memory inefficient constForAllFlow --- .../tac/fpcf/analyses/string/StringInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 4 ++-- .../L1VirtualMethodCallInterpreter.scala | 2 +- .../l2/interpretation/L2FieldReadInterpreter.scala | 2 +- .../L2VirtualFunctionCallInterpreter.scala | 2 +- .../fpcf/properties/string/StringFlowFunction.scala | 12 ++++++------ 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index e7db95a3c8..ae9ec93c1f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -49,7 +49,7 @@ object StringInterpreter { if (soundnessMode.isHigh) { computeFinalResult(StringFlowFunctionProperty.lb(state.pc, pv)) } else { - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, pv)) + computeFinalResult(StringFlowFunctionProperty.ub(state.pc, pv)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 3799fe3968..3839405b2b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -181,7 +181,7 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri } ) case _ => - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) + computeFinalResult(StringFlowFunctionProperty.ub(state.pc, at.get)) } case 2 => @@ -206,7 +206,7 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri } ) case _ => - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, at.get)) + computeFinalResult(StringFlowFunctionProperty.ub(state.pc, at.get)) } case _ => throw new IllegalStateException( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 5130ba636d..0d71ba197c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -54,7 +54,7 @@ object L1VirtualMethodCallInterpreter extends StringInterpreter { } ) case _ => - computeFinalResult(StringFlowFunctionProperty.noFlow(state.pc, pReceiver)) + computeFinalResult(StringFlowFunctionProperty.ub(state.pc, pReceiver)) } case _ => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index 6bdb104d40..6cdf65ca31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -131,7 +131,7 @@ class L2FieldReadInterpreter( accessState.previousResults.prepend(StringTreeNode.ub) InterimResult.forUB( InterpretationHandler.getEntity, - StringFlowFunctionProperty.ub, + StringFlowFunctionProperty.ub(state.pc, target), accessState.dependees.toSet, continuation(accessState, state) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index 08a0128a09..fa23ae8013 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -87,7 +87,7 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi if (depender.calleeDependee.isEPK) { InterimResult.forUB( InterpretationHandler.getEntity(state), - StringFlowFunctionProperty.ub, + StringFlowFunctionProperty.ub(state.pc, target), Set(depender.calleeDependee), continuation(state, depender) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 119a2c20aa..77f19bf118 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -43,28 +43,28 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat def ub: StringFlowFunctionProperty = constForAll(StringTreeInvalidElement) def identity: StringFlowFunctionProperty = - StringFlowFunctionProperty(Set.empty[PDUWeb], IdentityFlow) + StringFlowFunctionProperty(Set.empty[PDUWeb], (env: StringTreeEnvironment) => env) // Helps to register notable variable usage / definition which does not modify the current state def identityForVariableAt(pc: Int, v: PV): StringFlowFunctionProperty = - StringFlowFunctionProperty(pc, v, IdentityFlow) + StringFlowFunctionProperty(pc, v, (env: StringTreeEnvironment) => env) def lb(pc: Int, v: PV): StringFlowFunctionProperty = constForVariableAt(pc, v, StringTreeNode.lb) - def noFlow(pc: Int, v: PV): StringFlowFunctionProperty = + def ub(pc: Int, v: PV): StringFlowFunctionProperty = constForVariableAt(pc, v, StringTreeInvalidElement) def constForVariableAt(pc: Int, v: PV, result: StringTreeNode): StringFlowFunctionProperty = StringFlowFunctionProperty(pc, v, (env: StringTreeEnvironment) => env.update(pc, v, result)) def constForAll(result: StringTreeNode): StringFlowFunctionProperty = - StringFlowFunctionProperty(Set.empty[PDUWeb], (env: StringTreeEnvironment) => env.updateAll(result)) + StringFlowFunctionProperty(Set.empty[PDUWeb], ConstForAllFlow(result)) } trait StringFlowFunction extends (StringTreeEnvironment => StringTreeEnvironment) -object IdentityFlow extends StringFlowFunction { +case class ConstForAllFlow(result: StringTreeNode) extends StringFlowFunction { - override def apply(env: StringTreeEnvironment): StringTreeEnvironment = env + def apply(env: StringTreeEnvironment): StringTreeEnvironment = env.updateAll(result) } From 948c2553a1a8bf7334087d2fbb6b378c7d2543d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 22:44:24 +0200 Subject: [PATCH 509/583] Increase memory efficiency of data flow analysis --- .../flowanalysis/DataFlowAnalysis.scala | 50 +++++++++-------- .../string/StringTreeEnvironment.scala | 53 +++++++++++-------- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index f3cdcf28cd..6aec5b0c16 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -14,20 +14,19 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import scalax.collection.GraphTraversal.BreadthFirst import scalax.collection.GraphTraversal.Parameters -import scalax.collection.generic.Edge -import scalax.collection.immutable.Graph class DataFlowAnalysis( private val controlTree: ControlTree, private val superFlowGraph: SuperFlowGraph ) { + private val _nodeOrderings = mutable.Map.empty[FlowGraphNode, Seq[SuperFlowGraph#NodeT]] private val _removedBackEdgesGraphs = mutable.Map.empty[FlowGraphNode, (Boolean, SuperFlowGraph)] def compute( flowFunctionByPc: Map[Int, StringFlowFunction] )(startEnv: StringTreeEnvironment): StringTreeEnvironment = { - val startNodeCandidates = controlTree.nodes.filter(_.diPredecessors.isEmpty) + val startNodeCandidates = controlTree.nodes.filter(!_.hasPredecessors) if (startNodeCandidates.size != 1) { throw new IllegalStateException("Found more than one start node in the control tree!") } @@ -61,9 +60,8 @@ class DataFlowAnalysis( } def processIfThenElse(entry: FlowGraphNode): StringTreeEnvironment = { - val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) - val entryNode = limitedFlowGraph.get(entry) - val successors = entryNode.diSuccessors.map(_.outer).toList.sorted + val entryNode = superFlowGraph.get(entry) + val successors = entryNode.diSuccessors.intersect(innerChildNodes).map(_.outer).toList.sorted val branches = (successors.head, successors.tail.head) val envAfterEntry = pipe(entry, env) @@ -90,28 +88,35 @@ class DataFlowAnalysis( envAfterBranches._1.join(envAfterBranches._2) } - def handleProperSubregion[A <: FlowGraphNode, G <: Graph[A, Edge[A]]]( + def handleProperSubregion[A <: FlowGraphNode, G <: SuperFlowGraph]( g: G, innerNodes: Set[G#NodeT], entry: A ): StringTreeEnvironment = { val entryNode = g.get(entry) - val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) - val traverser = entryNode.innerNodeTraverser(Parameters(BreadthFirst)) - .withOrdering(ordering) - .withSubgraph(nodes = innerNodes.contains) - // We know that the graph is acyclic here, so we can be sure that the topological sort never fails - val sortedNodes = traverser.topologicalSort().toOption.get.toSeq + val sortedNodes = _nodeOrderings.getOrElseUpdate( + node, { + val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) + val traverser = entryNode.innerNodeTraverser(Parameters(BreadthFirst)) + .withOrdering(ordering) + .withSubgraph(nodes = innerNodes.contains) + // We know that the graph is acyclic here, so we can be sure that the topological sort never fails + traverser.topologicalSort().toOption.get.toSeq + } + ) val currentNodeEnvs = mutable.Map((entryNode, pipe(entry, env))) - for { currentNode <- sortedNodes.filter(_ != entryNode) } { + for { + _currentNode <- sortedNodes.filter(_ != entryNode) + currentNode = _currentNode.asInstanceOf[g.NodeT] + } { val previousEnvs = currentNode.diPredecessors.toList.sortBy(_.outer).map { dp => pipe(currentNode.outer, currentNodeEnvs(dp)) } currentNodeEnvs.update(currentNode, previousEnvs.head.joinMany(previousEnvs.tail)) } - currentNodeEnvs(sortedNodes.last) + currentNodeEnvs(sortedNodes.last.asInstanceOf[g.NodeT]) } def processProper(entry: FlowGraphNode): StringTreeEnvironment = { @@ -126,15 +131,18 @@ class DataFlowAnalysis( } def processWhileLoop(entry: FlowGraphNode): StringTreeEnvironment = { - val limitedFlowGraph = superFlowGraph.filter(innerChildNodes.contains) - val entryNode = limitedFlowGraph.get(entry) + val entryNode = superFlowGraph.get(entry) val envAfterEntry = pipe(entry, env) - var resultEnv = envAfterEntry - var currentNode = entryNode.diSuccessors.head - while (currentNode != entryNode) { + superFlowGraph.innerNodeTraverser(entryNode, subgraphNodes = innerChildNodes) + + var resultEnv = env + for { + currentNode <- superFlowGraph + .innerNodeTraverser(entryNode) + .withSubgraph(n => n != entryNode && innerChildNodes.contains(n)) + } { resultEnv = pipe(currentNode.outer, resultEnv) - currentNode = currentNode.diSuccessors.head } // Looped operations that modify environment contents are not supported here diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index fa52ba2b1d..e01cd19385 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -11,12 +11,19 @@ import org.opalj.br.fpcf.properties.string.StringTreeOr /** * @author Maximilian Rüsch */ -case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { +case class StringTreeEnvironment private ( + private val map: Map[PDUWeb, StringTreeNode], + private val pcToWebs: Map[Int, Iterable[PDUWeb]] +) { - private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = map.keys - .filter(_.containsVarAt(pc, pv)) - .toSeq - .sortBy(_.defPCs.toList.toSet.min) + private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = { + val webs = pv match { + case PDVar(_, _) => pcToWebs(pc) + case PUVar(_, varDefPCs) => varDefPCs.map(pcToWebs).flatten + } + + webs.toSeq.sortBy(_.defPCs.toList.toSet.min) + } private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys .filter(_.identifiesSameVarAs(web)) @@ -48,22 +55,15 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { def apply(web: PDUWeb): StringTreeNode = map(getWebFor(web)) def update(web: PDUWeb, value: StringTreeNode): StringTreeEnvironment = - StringTreeEnvironment(map.updated(getWebFor(web), value)) + recreate(map.updated(getWebFor(web), value)) def update(pc: Int, pv: PV, value: StringTreeNode): StringTreeEnvironment = - StringTreeEnvironment(map.updated(getWebFor(pc, pv), value)) + recreate(map.updated(getWebFor(pc, pv), value)) - def updateAll(value: StringTreeNode): StringTreeEnvironment = StringTreeEnvironment(map.map { kv => (kv._1, value) }) + def updateAll(value: StringTreeNode): StringTreeEnvironment = recreate(map.transform((_, _) => value)) def join(other: StringTreeEnvironment): StringTreeEnvironment = { - val (smallMap, bigMap) = if (map.size < other.map.size) (map, other.map) else (other.map, map) - - StringTreeEnvironment { - bigMap.toList.concat(smallMap.toList).groupBy(_._1).map { - case (k, vs) if vs.take(2).size == 1 => (k, vs.head._2) - case (k, vs) => (k, StringTreeOr.fromNodes(vs.map(_._2): _*)) - } - } + recreate(map.transform { (web, tree) => StringTreeOr.fromNodes(tree, other.map(web)) }) } def joinMany(others: List[StringTreeEnvironment]): StringTreeEnvironment = { @@ -71,13 +71,22 @@ case class StringTreeEnvironment(private val map: Map[PDUWeb, StringTreeNode]) { this } else { // This only works as long as environment maps are not sparse - StringTreeEnvironment(map.map { kv => - val otherValues = kv._2 +: others.map(_.map(kv._1)) - ( - kv._1, - StringTreeOr.fromNodes(otherValues: _*) - ) + recreate(map.transform { (web, tree) => + val otherValues = tree +: others.map(_.map(web)) + StringTreeOr.fromNodes(otherValues: _*) }) } } + + def recreate(newMap: Map[PDUWeb, StringTreeNode]): StringTreeEnvironment = StringTreeEnvironment(newMap, pcToWebs) +} + +object StringTreeEnvironment { + + def apply(map: Map[PDUWeb, StringTreeNode]): StringTreeEnvironment = { + val pcToWebs = + map.keys.flatMap(web => web.defPCs.map((_, web))).groupMap(_._1)(_._2) + .withDefaultValue(Iterable.empty) + StringTreeEnvironment(map, pcToWebs) + } } From dea60cbd3589acb19193debbb8b06326b0fcb21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 22:44:55 +0200 Subject: [PATCH 510/583] Increase memory efficiency of preparations for data flow analysis --- .../analyses/string/ComputationState.scala | 91 ++++++++++++++----- .../string/MethodStringFlowAnalysis.scala | 15 +-- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 1b3f2335e8..3223943378 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -8,8 +8,13 @@ package string import scala.collection.mutable +import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod import org.opalj.br.Method +import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree @@ -41,41 +46,83 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: private val pcToDependeeMapping: mutable.Map[Int, EOptionP[MethodPC, StringFlowFunctionProperty]] = mutable.Map.empty + private val pcToWebChangeMapping: mutable.Map[Int, Boolean] = mutable.Map.empty - def updateDependee(pc: Int, dependee: EOptionP[MethodPC, StringFlowFunctionProperty]): Unit = + def updateDependee(pc: Int, dependee: EOptionP[MethodPC, StringFlowFunctionProperty]): Unit = { + val prevOpt = pcToDependeeMapping.get(pc) + if (prevOpt.isEmpty || prevOpt.get.hasNoUBP || (dependee.hasUBP && prevOpt.get.ub.webs != dependee.ub.webs)) { + pcToWebChangeMapping.update(pc, true) + } pcToDependeeMapping.update(pc, dependee) + } def dependees: Set[EOptionP[MethodPC, StringFlowFunctionProperty]] = pcToDependeeMapping.values.filter(_.isRefinable).toSet def hasDependees: Boolean = pcToDependeeMapping.valuesIterator.exists(_.isRefinable) - def getFlowFunctionsByPC: Map[Int, StringFlowFunction] = pcToDependeeMapping.map { kv => - ( - kv._1, - if (kv._2.hasUBP) kv._2.ub.flow - else StringFlowFunctionProperty.ub.flow - ) - }.toMap - - def getWebs: Iterator[PDUWeb] = pcToDependeeMapping.values.flatMap { v => - if (v.hasUBP) v.ub.webs - else StringFlowFunctionProperty.ub.webs + def getFlowFunctionsByPC: Map[Int, StringFlowFunction] = pcToDependeeMapping.toMap.transform { (_, eOptP) => + if (eOptP.hasUBP) eOptP.ub.flow + else StringFlowFunctionProperty.ub.flow } - .toSeq.appendedAll(tac.params.parameters.zipWithIndex.map { + + private def getWebs: IndexedSeq[PDUWeb] = { + pcToDependeeMapping.values.flatMap { v => + if (v.hasUBP) v.ub.webs + else StringFlowFunctionProperty.ub.webs + }.toIndexedSeq.appendedAll(tac.params.parameters.zipWithIndex.map { case (param, index) => PDUWeb(IntTrieSet(-index - 1), if (param != null) param.useSites else IntTrieSet.empty) }) - .foldLeft(Seq.empty[PDUWeb]) { (reducedWebs, web) => - val mappedWebs = reducedWebs.map(w => (w, w.identifiesSameVarAs(web))) - if (!mappedWebs.exists(_._2)) { - reducedWebs :+ web - } else { - mappedWebs.filterNot(_._2).map(_._1) :+ mappedWebs.filter(_._2).map(_._1).reduce(_.combine(_)).combine( - web - ) + } + + private var _webMap: Map[PDUWeb, StringTreeNode] = Map.empty + def getWebMapAndReset: Map[PDUWeb, StringTreeNode] = { + if (pcToWebChangeMapping.exists(_._2)) { + val webs = getWebs + val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] + val defPCToWebIndex = mutable.Map.empty[Int, Int] + webs.foreach { web => + val existingDefPCs = web.defPCs.filter(defPCToWebIndex.contains) + if (existingDefPCs.nonEmpty) { + val indices = existingDefPCs.toList.map(defPCToWebIndex).distinct + if (indices.size == 1) { + val index = indices.head + indexedWebs.update(index, indexedWebs(index).combine(web)) + web.defPCs.foreach(defPCToWebIndex.update(_, index)) + } else { + val newIndex = indices.head + val originalWebs = indices.map(indexedWebs) + indexedWebs.update(newIndex, originalWebs.reduce(_.combine(_)).combine(web)) + indices.tail.foreach(indexedWebs.update(_, null)) + originalWebs.foreach(_.defPCs.foreach(defPCToWebIndex.update(_, newIndex))) + web.defPCs.foreach(defPCToWebIndex.update(_, newIndex)) + } + } else { + val newIndex = indexedWebs.length + indexedWebs.append(web) + web.defPCs.foreach(defPCToWebIndex.update(_, newIndex)) + } } - }.iterator + + _webMap = indexedWebs.filter(_ != null) + .map { web: PDUWeb => + val defPCs = web.defPCs.toList.sorted + if (defPCs.head >= 0) { + (web, StringTreeInvalidElement) + } else { + val pc = defPCs.head + if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { + (web, StringTreeDynamicString) + } else { + (web, StringTreeParameter.forParameterPC(pc)) + } + } + }.toMap + pcToWebChangeMapping.mapValuesInPlace((_, _) => false) + } + _webMap + } } case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index d88da7561f..ee2d7c3f00 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -114,20 +114,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } private def computeNewUpperBound(state: ComputationState): MethodStringFlow = { - val startEnv = StringTreeEnvironment(state.getWebs.map { web: PDUWeb => - val defPCs = web.defPCs.toList.sorted - if (defPCs.head >= 0) { - (web, StringTreeInvalidElement) - } else { - val pc = defPCs.head - if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - (web, StringTreeDynamicString) - } else { - (web, StringTreeParameter.forParameterPC(pc)) - } - } - }.toMap) - + val startEnv = StringTreeEnvironment(state.getWebMapAndReset) MethodStringFlow(state.flowAnalysis.compute(state.getFlowFunctionsByPC)(startEnv)) } } From ec64401c31a058ab71bb0550da5cc0a63777f705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 22:48:12 +0200 Subject: [PATCH 511/583] Improve memory efficiency of string trees using multi layer caching --- .../properties/string/StringTreeNode.scala | 132 ++++++++++++------ 1 file changed, 93 insertions(+), 39 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index e5a2e873c0..4d59b6f384 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -6,6 +6,7 @@ package properties package string import scala.collection.immutable.Seq +import scala.collection.mutable import scala.util.Try import scala.util.matching.Regex @@ -23,25 +24,56 @@ sealed trait StringTreeNode { def constancyLevel: StringConstancyLevel.Value def collectParameterIndices: Set[Int] = children.flatMap(_.collectParameterIndices).toSet - def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode + final def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { + if (parameters.isEmpty) this + else _replaceParameters(parameters) + } + protected def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode def isEmpty: Boolean = false def isInvalid: Boolean = false } +sealed trait CachedSimplifyNode extends StringTreeNode { + + private var _simplified = false + final def simplify: StringTreeNode = { + if (_simplified) { + this + } else { + _simplify match { + case cr: CachedSimplifyNode => + cr._simplified = true + cr + case r => r + } + } + } + + protected def _simplify: StringTreeNode +} + +sealed trait CachedHashCode extends Product { + + // Performance optimizations + private lazy val _hashCode = scala.util.hashing.MurmurHash3.productHash(this) + override def hashCode(): Int = _hashCode + override def canEqual(obj: Any): Boolean = obj.hashCode() == _hashCode +} + object StringTreeNode { def lb: StringTreeNode = StringTreeDynamicString def ub: StringTreeNode = StringTreeInvalidElement } -case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { +case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNode with CachedHashCode { override val children: Seq[StringTreeNode] = Seq(child) override def toRegex: String = s"(${child.toRegex})*" - override def simplify: StringTreeNode = { + override def _simplify: StringTreeNode = { val simplifiedChild = child.simplify if (simplifiedChild.isInvalid) StringTreeInvalidElement @@ -55,11 +87,11 @@ case class StringTreeRepetition(child: StringTreeNode) extends StringTreeNode { override def collectParameterIndices: Set[Int] = child.collectParameterIndices - def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = StringTreeRepetition(child.replaceParameters(parameters)) } -case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends StringTreeNode { +case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { override def toRegex: String = { children.size match { @@ -69,7 +101,7 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } } - override def simplify: StringTreeNode = { + override def _simplify: StringTreeNode = { val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty) if (nonEmptyChildren.exists(_.isInvalid)) { StringTreeInvalidElement @@ -91,8 +123,18 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends override def constancyLevel: StringConstancyLevel.Value = children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineForConcat) - def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = - StringTreeConcat(children.map(_.replaceParameters(parameters))) + def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { + val childrenWithChange = children.map { c => + val nc = c.replaceParameters(parameters) + (nc, c ne nc) + } + + if (childrenWithChange.exists(_._2)) { + StringTreeConcat(childrenWithChange.map(_._1)) + } else { + this + } + } } object StringTreeConcat { @@ -107,7 +149,8 @@ object StringTreeConcat { } } -case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends StringTreeNode { +case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends CachedSimplifyNode + with CachedHashCode { override def toRegex: String = { children.size match { @@ -117,30 +160,32 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } } - override def simplify: StringTreeNode = { - val validChildren = children.map(_.simplify).filterNot(_.isInvalid) - validChildren.size match { - case 0 => StringTreeInvalidElement - case 1 => validChildren.head - case _ => - var newChildren = Seq.empty[StringTreeNode] - validChildren.foreach { - case orChild: StringTreeOr => newChildren :++= orChild.children - case child => newChildren :+= child - } - val distinctNewChildren = newChildren.distinct - distinctNewChildren.size match { - case 1 => distinctNewChildren.head - case _ => StringTreeOr(distinctNewChildren) - } + override def _simplify: StringTreeNode = { + val validChildren = children.foldLeft(mutable.LinkedHashSet.empty[StringTreeNode]) { (set, child) => + val simpleChild = child.simplify + if (!simpleChild.isInvalid) + set += simpleChild + else + set } + StringTreeOr._simplifySelf(validChildren) } override def constancyLevel: StringConstancyLevel.Value = children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) - def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = - StringTreeOr(children.map(_.replaceParameters(parameters))) + def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { + val childrenWithChange = children.map { c => + val nc = c.replaceParameters(parameters) + (nc, c ne nc) + } + + if (childrenWithChange.exists(_._2)) { + StringTreeOr(childrenWithChange.map(_._1)) + } else { + this + } + } } object StringTreeOr { @@ -148,7 +193,7 @@ object StringTreeOr { def apply(children: Seq[StringTreeNode]): StringTreeNode = { if (children.isEmpty) { StringTreeInvalidElement - } else if (children.take(2).size == 1) { + } else if (children.size == 1) { children.head } else { new StringTreeOr(children) @@ -156,20 +201,29 @@ object StringTreeOr { } def fromNodes(children: StringTreeNode*): StringTreeNode = { - val validDistinctChildren = children.filterNot(_.isInvalid).distinct - validDistinctChildren.size match { + val validDistinctChildren = children + .foldLeft(mutable.LinkedHashSet.empty[StringTreeNode]) { (set, child) => + if (!child.isInvalid) + set += child + else + set + } + _simplifySelf(validDistinctChildren) + } + + private def _simplifySelf(_children: Iterable[StringTreeNode]): StringTreeNode = { + _children.size match { case 0 => StringTreeInvalidElement - case 1 => validDistinctChildren.head + case 1 => _children.head case _ => - var newChildren = Seq.empty[StringTreeNode] - validDistinctChildren.foreach { - case orChild: StringTreeOr => newChildren :++= orChild.children - case child => newChildren :+= child + val newChildren = _children.flatMap { + case orChild: StringTreeOr => orChild.children + case child => Iterable(child) } - val distinctNewChildren = newChildren.distinct + val distinctNewChildren = newChildren.foldLeft(mutable.LinkedHashSet.empty[StringTreeNode])(_ += _) distinctNewChildren.size match { case 1 => distinctNewChildren.head - case _ => StringTreeOr(distinctNewChildren) + case _ => StringTreeOr(distinctNewChildren.toSeq) } } } @@ -181,7 +235,7 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { override final def simplify: StringTreeNode = this - override def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this + override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this } case class StringTreeConst(string: String) extends SimpleStringTreeNode { @@ -204,7 +258,7 @@ case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { override def collectParameterIndices: Set[Int] = Set(index) - override def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = + override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = parameters.getOrElse(index, this) override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC From 645ab058c8e441da355f47e76985aa17f8a68bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 22:48:39 +0200 Subject: [PATCH 512/583] Remove unused imports --- .../tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index ee2d7c3f00..fa2899be63 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -5,15 +5,11 @@ package fpcf package analyses package string -import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString -import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement -import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EUBP import org.opalj.fpcf.FinalEP From 84978bac51551f47dea0dcecc1653e6c8ad15fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 23:19:37 +0200 Subject: [PATCH 513/583] Add regex caching and preliminary sorting on string tree node --- .../fixtures/string_analysis/Complex.java | 4 +- .../ExceptionalControlStructures.java | 16 ++++---- .../fixtures/string_analysis/External.java | 2 +- .../string_analysis/FunctionCalls.java | 10 ++--- .../fixtures/string_analysis/Integration.java | 2 +- .../fpcf/fixtures/string_analysis/Loops.java | 2 +- .../SimpleControlStructures.java | 4 +- .../SimpleStringBuilderOps.java | 2 +- .../string_analysis/SimpleStringOps.java | 16 ++++---- .../org/opalj/fpcf/StringAnalysisTest.scala | 6 +-- .../string_analysis/StringMatcher.scala | 2 +- .../string/StringConstancyProperty.scala | 2 +- .../properties/string/StringTreeNode.scala | 39 +++++++++++++------ 13 files changed, 62 insertions(+), 45 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java index 63c058649d..a3407bd135 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java @@ -19,7 +19,7 @@ public void analyzeString(String s) {} /** * Extracted from com.oracle.webservices.internal.api.message.BasePropertySet, has two def-sites and one use-site */ - @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(set.*|s.*)") + @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(s.*|set.*)") @Failure(n = 0, levels = Level.L0) public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { String name = getName; @@ -63,7 +63,7 @@ public void complexDependencyResolve(String s, Class clazz) { @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") @Failure(n = 0, levels = Level.L0) // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - @Constant(n = 0, levels = { Level.L1, Level.L2 }, value = "(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT|(Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest)") + @Constant(n = 0, levels = { Level.L1, Level.L2 }, value = "((Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest|Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)") public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { String shaderName = getHelloWorld() + "_" + "paintname"; if (getPaintType) { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java index 02cfd3b2ec..027aa15020 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java @@ -39,13 +39,13 @@ public void tryFinally(String filename) { @Failure(n = 2, levels = Level.L0) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "=====.*") // Exception case without own thrown exception - @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====)") + @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(=====.*=====|==========)") // The following cases are detected: - // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 3) - // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 1) - // 3. First append succeeds, throws no exception -> only first append (Pos 4) - // 4. First append is executed but throws an exception Throwable -> both appends (Pos 2) - @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(==========|=====.*=====|=====|=====.*)") + // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 1) + // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 4) + // 3. First append succeeds, throws no exception -> only first append (Pos 2) + // 4. First append is executed but throws an exception Throwable -> both appends (Pos 3) + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(=====|=====.*|=====.*=====|==========)") public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); try { @@ -62,8 +62,8 @@ public void tryCatchFinally(String filename) { @Failure(n = 1, levels = Level.L0) @Failure(n = 2, levels = Level.L0) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "BOS:.*") - @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") - @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS::EOS|BOS:.*:EOS)") + @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS:.*:EOS|BOS::EOS)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS:.*:EOS|BOS::EOS)") public void tryCatchFinallyWithThrowable(String filename) { StringBuilder sb = new StringBuilder("BOS:"); try { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index 4052024c7a..cb9b85a1ea 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -34,7 +34,7 @@ public External(float e) { */ public void analyzeString(String s) {} - @Constant(n = 0, levels = Level.TRUTH, value = "Field Value:private l0 non-final string field") + @Constant(n = 0, levels = Level.TRUTH, value = "private l0 non-final string field") @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void nonFinalFieldRead() { analyzeString(nonFinalNonStaticField); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java index 9402be3b88..ffff5a7730 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -65,7 +65,7 @@ public void functionWithFunctionParameter() { analyzeString(addQuestionMark(getHelloWorld())); } - @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ERROR|java.lang.Object|java.lang.StringBuilder)") @Constant(n = 0, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.LOW, value = "ERROR") @Dynamic(n = 0, levels = { Level.L0, Level.L1 }, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") public void simpleNonVirtualFunctionCallTestWithIf(int i) { @@ -80,7 +80,7 @@ public void simpleNonVirtualFunctionCallTestWithIf(int i) { analyzeString(s); } - @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.StringBuilder|ERROR)") + @Constant(n = 0, levels = Level.TRUTH, value = "(ERROR|java.lang.Object|java.lang.StringBuilder)") @Failure(n = 0, levels = Level.L0) @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "ERROR") @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|ERROR)") @@ -112,7 +112,7 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { /** * A case where the single valid return value of the called function can be resolved without calling the function. */ - @Constant(n = 0, levels = Level.TRUTH, domains = DomainLevel.L1, value = "(java.lang.Object|One|val)") + @Constant(n = 0, levels = Level.TRUTH, domains = DomainLevel.L1, value = "(One|java.lang.Object|val)") @Failure(n = 0, levels = { Level.L0, Level.L1 }, domains = DomainLevel.L1) // Since the virtual function return value is inlined in L2 and its actual runtime return // value is not used, the function call gets converted to a method call, which modifies the @@ -137,7 +137,7 @@ private String resolvableReturnValueFunction(String s, int i) { } } - @Constant(n = 0, levels = Level.TRUTH, value = "(One|val|java.lang.Object)") + @Constant(n = 0, levels = Level.TRUTH, value = "(One|java.lang.Object|val)") @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void severalReturnValuesTest1() { analyzeString(severalReturnValuesWithSwitchFunction("val", 42)); @@ -152,7 +152,7 @@ private String severalReturnValuesWithSwitchFunction(String s, int i) { } } - @Constant(n = 0, levels = Level.TRUTH, value = "(that's odd|Hello, World)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello, World|that's odd)") @Failure(n = 0, levels = Level.L0) public void severalReturnValuesTest2() { analyzeString(severalReturnValuesWithIfElseFunction(42)); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java index fbcd243212..27b697e4f9 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java @@ -36,7 +36,7 @@ public void knownHierarchyInstanceTest() { analyzeString(gs.getGreeting("World")); } - @Constant(n = 0, levels = Level.TRUTH, value = "(Hello World|Hello)") + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello|Hello World)") @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java index e695c23b35..d2a92a3612 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -157,7 +157,7 @@ public void extensiveWithManyControlStructures(boolean cond) { @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") @Constant(n = 1, levels = Level.TRUTH, value = "") @Dynamic(n = 1, levels = Level.L0, value = ".*") - @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, value = "(.*|)") + @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, value = "(|.*)") @Dynamic(n = 2, levels = Level.TRUTH, value = "((.*)?)*") @Dynamic(n = 2, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") public void breakContinueExamples(int value) { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java index 746ca02eb2..22052565b7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java @@ -15,7 +15,7 @@ public class SimpleControlStructures { */ public void analyzeString(String s) {} - @Dynamic(n = 0, levels = Level.TRUTH, value = "(x|^-?\\d+$)") + @Dynamic(n = 0, levels = Level.TRUTH, value = "(^-?\\d+$|x)") @Failure(n = 0, levels = Level.L0) @Constant(n = 1, levels = Level.TRUTH, value = "(42-42|x)") @Failure(n = 1, levels = Level.L0) @@ -87,7 +87,7 @@ public void ifElseWithStringBuilderWithMultipleAppends() { analyzeString(sb.toString()); } - @Constant(n = 0, levels = Level.TRUTH, value = "(abcd|a|axyz)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|abcd|axyz)") @Failure(n = 0, levels = Level.L0) public void ifElseWithStringBuilderWithMultipleAppendsAndNonUsedElseIf() { StringBuilder sb = new StringBuilder("a"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java index fb43242513..777a1b52ce 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java @@ -155,7 +155,7 @@ public void simpleSecondStringBuilderRead(String className) { @Constant(n = 0, levels = Level.TRUTH, value = "(Object|ObjectRuntime)") @Failure(n = 0, levels = Level.L0) - @Constant(n = 1, levels = Level.TRUTH, value = "(RuntimeObject|Runtime)") + @Constant(n = 1, levels = Level.TRUTH, value = "(Runtime|RuntimeObject)") @Failure(n = 1, levels = Level.L0) public void crissCrossExample(String className) { StringBuilder sbObj = new StringBuilder("Object"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java index bf8ba123b0..d7977f11ac 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java @@ -89,7 +89,7 @@ public void simpleSubstring() { analyzeString(someString.substring(2)); } - @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.System|java.lang.Runtime)") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Runtime|java.lang.System)") public void multipleConstantDefSites(boolean cond) { String s; if (cond) { @@ -114,9 +114,9 @@ public void appendWithTwoDefSites(int i) { @Constant(n = 0, levels = Level.TRUTH, value = "(Some|SomeOther)") @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "Some") - @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(Some|.*)") + @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|Some)") @Dynamic(n = 1, levels = Level.TRUTH, value = "(.*|Some)") - @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "(SomeOther|Some.*)") + @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "(Some.*|SomeOther)") @Failure(n = 2, levels = Level.L0) public void ternaryOperators(boolean flag, String param) { String s1 = "Some"; @@ -146,7 +146,7 @@ public void switchRelevantAndIrrelevant(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|a|ad)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad)") @Failure(n = 0, levels = Level.L0) public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { StringBuilder sb = new StringBuilder("a"); @@ -207,7 +207,7 @@ public void switchRelevantWithRelevantDefault(int value) { analyzeString(sb.toString()); } - @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|a|ad|af)") + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad|af)") @Failure(n = 0, levels = Level.L0) public void switchNestedNoNestedDefault(int value, int value2) { StringBuilder sb = new StringBuilder("a"); @@ -268,9 +268,9 @@ public void switchNestedWithNestedDefault(int value, int value2) { @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "java.lang.System") @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System)") @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "java.lang.System") - @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang..*)") - @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") - @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System|java.lang.StringBuilder|java.lang.StringBuilder)") + @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang..*|java.lang.System)") + @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") + @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; 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 31bb0a2890..55b163d560 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 @@ -332,19 +332,19 @@ sealed abstract class L2StringAnalysisTest extends StringAnalysisTest { } } -class L2StringAnalysisWithL1DefaultDomainTest extends L1StringAnalysisTest { +class L2StringAnalysisWithL1DefaultDomainTest extends L2StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW } -class L2StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { +class L2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW } -class HighSoundnessL2StringAnalysisWithL2DefaultDomainTest extends L1StringAnalysisTest { +class HighSoundnessL2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala index 61009a51ea..9b328e4903 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala @@ -17,7 +17,7 @@ sealed trait StringMatcher extends AbstractPropertyMatcher { protected def getActualValues: Property => Option[(String, String)] = { case prop: StringConstancyProperty => - val tree = prop.sci.tree.simplify + val tree = prop.sci.tree.simplify.sorted if (tree.isInvalid) { None } else { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index 3c80d833a6..c1a632d316 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -28,7 +28,7 @@ class StringConstancyProperty( val level = stringConstancyInformation.constancyLevel.toString.toLowerCase val strings = if (stringConstancyInformation.tree.simplify.isInvalid) { "No possible strings - Invalid Flow" - } else stringConstancyInformation.toRegex + } else stringConstancyInformation.tree.sorted.toRegex s"Level: $level, Possible Strings: $strings" } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 4d59b6f384..50a27921ca 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -17,7 +17,17 @@ sealed trait StringTreeNode { val children: Seq[StringTreeNode] - def toRegex: String + def sorted: StringTreeNode + + private var _regex: Option[String] = None + final def toRegex: String = { + if (_regex.isEmpty) { + _regex = Some(_toRegex) + } + + _regex.get + } + def _toRegex: String def simplify: StringTreeNode @@ -71,7 +81,9 @@ case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNod override val children: Seq[StringTreeNode] = Seq(child) - override def toRegex: String = s"(${child.toRegex})*" + override def _toRegex: String = s"(${child.toRegex})*" + + override def sorted: StringTreeNode = this override def _simplify: StringTreeNode = { val simplifiedChild = child.simplify @@ -93,7 +105,7 @@ case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNod case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { - override def toRegex: String = { + override def _toRegex: String = { children.size match { case 0 => throw new IllegalStateException("Tried to convert StringTreeConcat with no children to a regex!") case 1 => children.head.toRegex @@ -101,6 +113,8 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } } + override def sorted: StringTreeNode = this + override def _simplify: StringTreeNode = { val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty) if (nonEmptyChildren.exists(_.isInvalid)) { @@ -152,7 +166,7 @@ object StringTreeConcat { case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { - override def toRegex: String = { + override def _toRegex: String = { children.size match { case 0 => throw new IllegalStateException("Tried to convert StringTreeOr with no children to a regex!") case 1 => children.head.toRegex @@ -160,6 +174,8 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } } + override def sorted: StringTreeNode = StringTreeOr(children.sortBy(_.toRegex)) + override def _simplify: StringTreeNode = { val validChildren = children.foldLeft(mutable.LinkedHashSet.empty[StringTreeNode]) { (set, child) => val simpleChild = child.simplify @@ -233,13 +249,14 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { override final val children: Seq[StringTreeNode] = Seq.empty + override final def sorted: StringTreeNode = this override final def simplify: StringTreeNode = this override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this } case class StringTreeConst(string: String) extends SimpleStringTreeNode { - override def toRegex: String = Regex.quoteReplacement(string) + override def _toRegex: String = Regex.quoteReplacement(string) override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT @@ -254,7 +271,7 @@ object StringTreeEmptyConst extends StringTreeConst("") { } case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { - override def toRegex: String = ".*" + override def _toRegex: String = ".*" override def collectParameterIndices: Set[Int] = Set(index) @@ -276,7 +293,7 @@ object StringTreeParameter { } object StringTreeInvalidElement extends SimpleStringTreeNode { - override def toRegex: String = throw new UnsupportedOperationException() + override def _toRegex: String = throw new UnsupportedOperationException() override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT @@ -285,25 +302,25 @@ object StringTreeInvalidElement extends SimpleStringTreeNode { object StringTreeNull extends SimpleStringTreeNode { // Using this element nested in some other element might lead to unexpected results... - override def toRegex: String = "^null$" + override def _toRegex: String = "^null$" override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT } object StringTreeDynamicString extends SimpleStringTreeNode { - override def toRegex: String = ".*" + override def _toRegex: String = ".*" override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC } object StringTreeDynamicInt extends SimpleStringTreeNode { - override def toRegex: String = "^-?\\d+$" + override def _toRegex: String = "^-?\\d+$" override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC } object StringTreeDynamicFloat extends SimpleStringTreeNode { - override def toRegex: String = "^-?\\d*\\.{0,1}\\d+$" + override def _toRegex: String = "^-?\\d*\\.{0,1}\\d+$" override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC } From 980df1265c9b108e668f50a84d251783497e0cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 7 Aug 2024 23:50:27 +0200 Subject: [PATCH 514/583] Stabilize string tree node ordering by introducing set and seq based or nodes --- .../properties/string/StringTreeNode.scala | 123 +++++++++++++----- 1 file changed, 90 insertions(+), 33 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 50a27921ca..41bb3e1270 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -6,7 +6,6 @@ package properties package string import scala.collection.immutable.Seq -import scala.collection.mutable import scala.util.Try import scala.util.matching.Regex @@ -163,8 +162,11 @@ object StringTreeConcat { } } -case class StringTreeOr private (override val children: Seq[StringTreeNode]) extends CachedSimplifyNode - with CachedHashCode { +trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { + + protected val _children: Iterable[StringTreeNode] + + override final val children: Seq[StringTreeNode] = _children.toSeq override def _toRegex: String = { children.size match { @@ -174,37 +176,66 @@ case class StringTreeOr private (override val children: Seq[StringTreeNode]) ext } } - override def sorted: StringTreeNode = StringTreeOr(children.sortBy(_.toRegex)) + override def constancyLevel: StringConstancyLevel.Value = + _children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) - override def _simplify: StringTreeNode = { - val validChildren = children.foldLeft(mutable.LinkedHashSet.empty[StringTreeNode]) { (set, child) => - val simpleChild = child.simplify - if (!simpleChild.isInvalid) - set += simpleChild - else - set + override def collectParameterIndices: Set[Int] = _children.flatMap(_.collectParameterIndices).toSet +} + +object StringTreeOr { + + def apply(children: Seq[StringTreeNode]): StringTreeNode = apply(children.toSet) + + def apply(children: Set[StringTreeNode]): StringTreeNode = { + if (children.isEmpty) { + StringTreeInvalidElement + } else if (children.size == 1) { + children.head + } else { + new SetBasedStringTreeOr(children) } - StringTreeOr._simplifySelf(validChildren) } - override def constancyLevel: StringConstancyLevel.Value = - children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) + def fromNodes(children: StringTreeNode*): StringTreeNode = SetBasedStringTreeOr.createWithSimplify(children.toSet) +} + +case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNode]) extends StringTreeOr { + + override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.sortBy(_.sorted.toRegex)) + + override def _simplify: StringTreeNode = { + val validChildren = _children.map(_.simplify).filterNot(_.isInvalid) + validChildren.size match { + case 0 => StringTreeInvalidElement + case 1 => validChildren.head + case _ => + val newChildren = validChildren.flatMap { + case orChild: StringTreeOr => orChild.children + case child => Set(child) + } + val distinctNewChildren = newChildren.distinct + distinctNewChildren.size match { + case 1 => distinctNewChildren.head + case _ => SeqBasedStringTreeOr(distinctNewChildren) + } + } + } def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { - val childrenWithChange = children.map { c => + val childrenWithChange = _children.map { c => val nc = c.replaceParameters(parameters) (nc, c ne nc) } if (childrenWithChange.exists(_._2)) { - StringTreeOr(childrenWithChange.map(_._1)) + SeqBasedStringTreeOr(childrenWithChange.map(_._1)) } else { this } } } -object StringTreeOr { +object SeqBasedStringTreeOr { def apply(children: Seq[StringTreeNode]): StringTreeNode = { if (children.isEmpty) { @@ -212,34 +243,60 @@ object StringTreeOr { } else if (children.size == 1) { children.head } else { - new StringTreeOr(children) + new SeqBasedStringTreeOr(children) } } +} - def fromNodes(children: StringTreeNode*): StringTreeNode = { - val validDistinctChildren = children - .foldLeft(mutable.LinkedHashSet.empty[StringTreeNode]) { (set, child) => - if (!child.isInvalid) - set += child - else - set - } - _simplifySelf(validDistinctChildren) +case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) extends StringTreeOr { + + override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.sortBy(_.sorted.toRegex)) + + override def _simplify: StringTreeNode = SetBasedStringTreeOr._simplifySelf { + _children.map(_.simplify).filterNot(_.isInvalid) + } + + def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { + val childrenWithChange = _children.map { c => + val nc = c.replaceParameters(parameters) + (nc, c ne nc) + } + + if (childrenWithChange.exists(_._2)) { + SetBasedStringTreeOr(childrenWithChange.map(_._1)) + } else { + this + } } +} + +object SetBasedStringTreeOr { - private def _simplifySelf(_children: Iterable[StringTreeNode]): StringTreeNode = { + def apply(children: Set[StringTreeNode]): StringTreeNode = { + if (children.isEmpty) { + StringTreeInvalidElement + } else if (children.size == 1) { + children.head + } else { + new SetBasedStringTreeOr(children) + } + } + + def createWithSimplify(children: Set[StringTreeNode]): StringTreeNode = + _simplifySelf(children.filterNot(_.isInvalid)) + + private def _simplifySelf(_children: Set[StringTreeNode]): StringTreeNode = { _children.size match { case 0 => StringTreeInvalidElement case 1 => _children.head case _ => val newChildren = _children.flatMap { case orChild: StringTreeOr => orChild.children - case child => Iterable(child) + case child => Set(child) } - val distinctNewChildren = newChildren.foldLeft(mutable.LinkedHashSet.empty[StringTreeNode])(_ += _) - distinctNewChildren.size match { - case 1 => distinctNewChildren.head - case _ => StringTreeOr(distinctNewChildren.toSeq) + newChildren.size match { + case 1 => newChildren.head + case _ => SetBasedStringTreeOr(newChildren) } } } From 22cb723b6ef3ec50da1aa27adbb3f38218e9f761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:02:59 +0200 Subject: [PATCH 515/583] Cache more of the start env if possible --- .../analyses/string/ComputationState.scala | 11 ++++++----- .../string/MethodStringFlowAnalysis.scala | 19 +++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala index 3223943378..4ef05c4711 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala @@ -13,7 +13,6 @@ import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement -import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.EOptionP @@ -24,6 +23,7 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty +import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * This class is to be used to store state information that are required at a later point in @@ -76,8 +76,8 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: }) } - private var _webMap: Map[PDUWeb, StringTreeNode] = Map.empty - def getWebMapAndReset: Map[PDUWeb, StringTreeNode] = { + private var _startEnv: StringTreeEnvironment = StringTreeEnvironment(Map.empty) + def getStartEnvAndReset: StringTreeEnvironment = { if (pcToWebChangeMapping.exists(_._2)) { val webs = getWebs val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] @@ -105,7 +105,7 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: } } - _webMap = indexedWebs.filter(_ != null) + val startMap = indexedWebs.filter(_ != null) .map { web: PDUWeb => val defPCs = web.defPCs.toList.sorted if (defPCs.head >= 0) { @@ -119,9 +119,10 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: } } }.toMap + _startEnv = StringTreeEnvironment(startMap) pcToWebChangeMapping.mapValuesInPlace((_, _) => false) } - _webMap + _startEnv } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index fa2899be63..29fd6762de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -25,7 +25,6 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty -import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch @@ -94,23 +93,19 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn private def computeResults(implicit state: ComputationState): ProperPropertyComputationResult = { if (state.hasDependees) { - getInterimResult(state) + InterimResult.forUB( + state.entity, + computeNewUpperBound(state), + state.dependees.toSet, + continuation(state) + ) } else { Result(state.entity, computeNewUpperBound(state)) } } - private def getInterimResult(state: ComputationState): InterimResult[MethodStringFlow] = { - InterimResult.forUB( - state.entity, - computeNewUpperBound(state), - state.dependees.toSet, - continuation(state) - ) - } - private def computeNewUpperBound(state: ComputationState): MethodStringFlow = { - val startEnv = StringTreeEnvironment(state.getWebMapAndReset) + val startEnv = state.getStartEnvAndReset MethodStringFlow(state.flowAnalysis.compute(state.getFlowFunctionsByPC)(startEnv)) } } From dab03786b08a1d73527cbb547ede5c349df28706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:03:10 +0200 Subject: [PATCH 516/583] Improve sorting and fix tests related to sorting --- .../opalj/fpcf/fixtures/string_analysis/FunctionCalls.java | 2 +- .../opalj/br/fpcf/properties/string/StringTreeNode.scala | 6 +++--- .../analyses/string/flowanalysis/DataFlowAnalysis.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java index ffff5a7730..aab59ee23c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -97,7 +97,7 @@ public void initFromNonVirtualFunctionCallTest(int i) { analyzeString(sb.toString()); } - @Constant(n = 0, levels = Level.TRUTH, value = "It is (great|Hello, World)") + @Constant(n = 0, levels = Level.TRUTH, value = "It is (Hello, World|great)") @Failure(n = 0, levels = Level.L0) public void appendWithTwoDefSitesWithFuncCallTest(int i) { String s; diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 41bb3e1270..d052504a57 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -112,7 +112,7 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } } - override def sorted: StringTreeNode = this + override def sorted: StringTreeNode = StringTreeConcat(children.map(_.sorted)) override def _simplify: StringTreeNode = { val nonEmptyChildren = children.map(_.simplify).filterNot(_.isEmpty) @@ -201,7 +201,7 @@ object StringTreeOr { case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNode]) extends StringTreeOr { - override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.sortBy(_.sorted.toRegex)) + override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.map(_.sorted).sortBy(_.toRegex)) override def _simplify: StringTreeNode = { val validChildren = _children.map(_.simplify).filterNot(_.isInvalid) @@ -250,7 +250,7 @@ object SeqBasedStringTreeOr { case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) extends StringTreeOr { - override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.sortBy(_.sorted.toRegex)) + override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.map(_.sorted).sortBy(_.toRegex)) override def _simplify: StringTreeNode = SetBasedStringTreeOr._simplifySelf { _children.map(_.simplify).filterNot(_.isInvalid) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 6aec5b0c16..277af0c48c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -110,7 +110,7 @@ class DataFlowAnalysis( _currentNode <- sortedNodes.filter(_ != entryNode) currentNode = _currentNode.asInstanceOf[g.NodeT] } { - val previousEnvs = currentNode.diPredecessors.toList.sortBy(_.outer).map { dp => + val previousEnvs = currentNode.diPredecessors.toList.map { dp => pipe(currentNode.outer, currentNodeEnvs(dp)) } currentNodeEnvs.update(currentNode, previousEnvs.head.joinMany(previousEnvs.tail)) From 0da7ef5a95fb3b4de2c7012344c01e9a5d971811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:03:12 +0200 Subject: [PATCH 517/583] Add performance caching for PDUWebs --- OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala index ee38324545..e09b08e9fe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala @@ -9,16 +9,16 @@ case class PDUWeb( defPCs: IntTrieSet, usePCs: IntTrieSet ) { - def containsVarAt(pc: Int, pv: PV): Boolean = pv match { - case PDVar(_, _) => defPCs.contains(pc) - case PUVar(_, varDefPCs) => defPCs.intersect(varDefPCs).nonEmpty - } - def identifiesSameVarAs(other: PDUWeb): Boolean = other.defPCs.intersect(defPCs).nonEmpty def combine(other: PDUWeb): PDUWeb = PDUWeb(other.defPCs ++ defPCs, other.usePCs ++ usePCs) def size: Int = defPCs.size + usePCs.size + + // Performance optimizations + private lazy val _hashCode = scala.util.hashing.MurmurHash3.productHash(this) + override def hashCode(): Int = _hashCode + override def equals(obj: Any): Boolean = obj.hashCode() == _hashCode && super.equals(obj) } object PDUWeb { From a2421fdc0fecf9861e3f223e526d835f829c0179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:03:14 +0200 Subject: [PATCH 518/583] Improve joining environments --- .../fpcf/properties/string/StringTreeEnvironment.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index e01cd19385..b40e9fd630 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -5,6 +5,7 @@ package fpcf package properties package string +import org.opalj.br.fpcf.properties.string.SetBasedStringTreeOr import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr @@ -63,17 +64,17 @@ case class StringTreeEnvironment private ( def updateAll(value: StringTreeNode): StringTreeEnvironment = recreate(map.transform((_, _) => value)) def join(other: StringTreeEnvironment): StringTreeEnvironment = { - recreate(map.transform { (web, tree) => StringTreeOr.fromNodes(tree, other.map(web)) }) + recreate(map.transform { (web, tree) => SetBasedStringTreeOr.createWithSimplify(Set(tree, other.map(web))) }) } - def joinMany(others: List[StringTreeEnvironment]): StringTreeEnvironment = { + def joinMany(others: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { if (others.isEmpty) { this } else { // This only works as long as environment maps are not sparse recreate(map.transform { (web, tree) => - val otherValues = tree +: others.map(_.map(web)) - StringTreeOr.fromNodes(otherValues: _*) + val otherValues = others.map(_.map(web)).concat(Iterable(tree)) + SetBasedStringTreeOr.createWithSimplify(otherValues.toSet) }) } } From 3de6534f58bee7d7bea0740eb38fa82535b0e6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:03:17 +0200 Subject: [PATCH 519/583] Rework context string analysis for more controlled continuation --- .../fpcf/analyses/string/StringAnalysis.scala | 159 ++++++++++-------- .../analyses/string/StringAnalysisState.scala | 126 +++++++++----- 2 files changed, 177 insertions(+), 108 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index a9609e77ae..d9b6c2ea2f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -20,6 +20,7 @@ import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK +import org.opalj.fpcf.EPS import org.opalj.fpcf.EUBP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -84,12 +85,17 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly val vdScp = ps(VariableDefinition(vc.pc, vc.pv, vc.m), StringConstancyProperty.key) implicit val state: ContextStringAnalysisState = ContextStringAnalysisState(vc, vdScp) - if (vdScp.isEPK) { - state._stringDependee = vdScp - computeResults - } else { - continuation(state)(vdScp.asInstanceOf[SomeEPS]) + if (state.parameterIndices.nonEmpty) { + // We have some parameters that need to be resolved for all callers of this method + val callersEOptP = ps(state.dm, Callers.key) + state.updateCallers(callersEOptP) + + if (callersEOptP.hasUBP) { + handleNewCallers(NoCallers, callersEOptP.ub) + } } + + computeResults } private def continuation(state: ContextStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { @@ -97,31 +103,21 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly eps match { // "Downwards" dependency case EUBP(_: VariableDefinition, _: StringConstancyProperty) => - state._stringDependee = eps.asInstanceOf[EOptionP[VariableDefinition, StringConstancyProperty]] - - val parameterIndices = state.stringTree.collectParameterIndices - if (parameterIndices.isEmpty) { - computeResults - } else { - // We have some parameters that need to be resolved for all callers of this method - val callersEOptP = ps(state.entity.context.method, Callers.key) - state._callersDependee = Some(callersEOptP) - - if (callersEOptP.hasUBP) { - handleNewCallers(NoCallers, callersEOptP.ub) - } - - computeResults - } + handleStringDefinitionUpdate(eps.asInstanceOf[EPS[VariableDefinition, StringConstancyProperty]]) + computeResults case UBP(callers: Callers) => val oldCallers = state._callersDependee.get.ub - state._callersDependee = Some(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) + state.updateCallers(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) handleNewCallers(oldCallers, callers) computeResults case EUBP(m: Method, tacai: TACAI) => - handleTACAI(m, tacai) + if (tacai.tac.isEmpty) { + state._discoveredUnknownTAC = true + } else { + state.getCallerContexts(m).foreach(handleTACForContext(tacai.tac.get, _)) + } computeResults // "Upwards" dependency @@ -134,6 +130,33 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly } } + private def handleStringDefinitionUpdate( + newDefinitionEPS: EPS[VariableDefinition, StringConstancyProperty] + )(implicit state: ContextStringAnalysisState): Unit = { + val previousIndices = state.parameterIndices + state.updateStringDependee(newDefinitionEPS) + val newIndices = state.parameterIndices + + if (newIndices.nonEmpty && newIndices != previousIndices) { + if (previousIndices.isEmpty) { + // We have some parameters that need to be resolved for all callers of this method + val callersEOptP = ps(state.dm, Callers.key) + state.updateCallers(callersEOptP) + if (callersEOptP.hasUBP) { + handleNewCallers(NoCallers, callersEOptP.ub) + } + } else { + for { + (context, callExprOpt) <- state._callerContexts + if callExprOpt.isDefined + index <- newIndices.diff(previousIndices) + } { + handleIndexForCallExpr(context, callExprOpt.get)(index) + } + } + } + } + private def handleNewCallers( oldCallers: Callers, newCallers: Callers @@ -145,62 +168,62 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly } } - for { (context, pc) <- relevantCallerContexts } { - val callerMethod = context.method.definedMethod - - val tacEOptP = ps(callerMethod, TACAI.key) - state.registerTacaiDepender(tacEOptP, (context, pc)) - + for { callerContext <- relevantCallerContexts } { + state.addCallerContext(callerContext) + val tacEOptP = state.getTacaiForContext(callerContext) if (tacEOptP.hasUBP) { - handleTACAI(callerMethod, tacEOptP.ub) + val tacOpt = tacEOptP.ub.tac + if (tacOpt.isEmpty) { + state._discoveredUnknownTAC = true + } else { + handleTACForContext(tacOpt.get, callerContext) + } } } } - private def handleTACAI(m: Method, tacai: TACAI)( - implicit state: ContextStringAnalysisState - ): Unit = { - if (tacai.tac.isEmpty) { - state._discoveredUnknownTAC = true - } else { - val tac = tacai.tac.get - val (callerContext, pc) = state.getDepender(m) - val callExpr = tac.stmts(valueOriginOfPC(pc, tac.pcToIndex).get) match { - case Assignment(_, _, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] - case ExprStmt(_, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] - case call: Call[_] => call.asInstanceOf[Call[V]] - case node => throw new IllegalArgumentException(s"Unexpected argument: $node") - } + private def handleTACForContext( + tac: TAC, + context: (Context, Int) + )(implicit state: ContextStringAnalysisState): Unit = { + val callExpr = tac.stmts(valueOriginOfPC(context._2, tac.pcToIndex).get) match { + case Assignment(_, _, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] + case ExprStmt(_, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] + case call: Call[_] => call.asInstanceOf[Call[V]] + case node => throw new IllegalArgumentException(s"Unexpected argument: $node") + } - for { - index <- state.stringTree.collectParameterIndices - } { - if (index >= callExpr.params.size) { - OPALLogger.warn( - "string analysis", - s"Found parameter reference $index with insufficient parameters during analysis of call: " - + s"${state.entity.m.toJava} in method ${m.toJava}" - ) - state.registerInvalidParamReference(index, m) - } else { - val paramVC = VariableContext( - pc, - callExpr.params(index).asVar.toPersistentForm(tac.stmts), - callerContext - ) - state.registerParameterDependee(index, m, ps(paramVC, StringConstancyProperty.key)) - } - } + val previousCallExpr = state.addCallExprInformationForContext(context, callExpr) + if (previousCallExpr.isEmpty || previousCallExpr.get != callExpr) { + state.parameterIndices.foreach(handleIndexForCallExpr(context, callExpr)) } } - private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { - if (state.dm.name == "newInstance" && state.dm.declaringClassType.fqn.endsWith("FactoryFinder")) { - System.out.println("DAMN") - } else if (state.dm.name == "find" && state.dm.declaringClassType.fqn.endsWith("FactoryFinder")) { - System.out.println("DAMN2") + private def handleIndexForCallExpr( + callerContext: (Context, Int), + callExpr: Call[V] + )(index: Int)(implicit state: ContextStringAnalysisState): Unit = { + val dm = callerContext._1.method.asDefinedMethod + if (index >= callExpr.params.size) { + OPALLogger.warn( + "string analysis", + s"Found parameter reference $index with insufficient parameters during analysis of call: " + + s"${state.dm.id} in method ID ${dm.id} at PC ${callerContext._2}." + ) + state.registerInvalidParamReference(index, dm.id) + } else { + val tac = state.getTACForContext(callerContext) + val paramVC = VariableContext( + callerContext._2, + callExpr.params(index).asVar.toPersistentForm(tac.stmts), + callerContext._1 + ) + + state.registerParameterDependee(index, dm, ps(paramVC, StringConstancyProperty.key)) } + } + private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { if (state.hasDependees) { InterimResult( state.entity, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 901d9366a3..44ac29bf26 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -8,6 +8,7 @@ package string import scala.collection.mutable import org.opalj.br.DeclaredMethod +import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers @@ -17,7 +18,9 @@ import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPS import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow @@ -31,75 +34,109 @@ private[string] case class ContextFreeStringAnalysisState( def dependees: Set[EOptionP[Entity, Property]] = Set(stringFlowDependee) } -private[string] case class ContextStringAnalysisState( - entity: VariableContext, - var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] +private[string] class ContextStringAnalysisState private ( + _entity: VariableContext, + var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty], + private var _parameterIndices: Set[Int] ) { - def dm: DeclaredMethod = entity.context.method - def stringTree: StringTreeNode = _stringDependee.ub.sci.tree + def entity: VariableContext = _entity + def dm: DeclaredMethod = _entity.context.method + + def updateStringDependee(stringDependee: EPS[VariableDefinition, StringConstancyProperty]): Unit = { + _stringDependee = stringDependee + // TODO parameter indices should always grow <- when the new string dependee only contains additional information + _parameterIndices ++= stringDependee.ub.sci.tree.collectParameterIndices + } + private def stringTree: StringTreeNode = _stringDependee.ub.sci.tree + def parameterIndices: Set[Int] = _parameterIndices // Callers + private[string] type CallerContext = (Context, Int) var _callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = None + val _callerContexts: mutable.Map[CallerContext, Option[Call[V]]] = mutable.Map.empty + private val _callerContextsByMethod: mutable.Map[Method, Seq[CallerContext]] = mutable.Map.empty + + def updateCallers(newCallers: EOptionP[DeclaredMethod, Callers]): Unit = { + _callersDependee = Some(newCallers) + } + + def addCallerContext(callerContext: CallerContext): Unit = { + _callerContexts.update(callerContext, None) + _callerContextsByMethod.updateWith(callerContext._1.method.definedMethod) { + case None => Some(Seq(callerContext)) + case Some(prev) => Some(callerContext +: prev) + } + } + def getCallerContexts(m: Method): Seq[CallerContext] = _callerContextsByMethod(m) + def addCallExprInformationForContext(callerContext: CallerContext, expr: Call[V]): Option[Call[V]] = + _callerContexts.put(callerContext, Some(expr)).flatten // TACAI - private type TACAIDepender = (Context, Int) private val _tacaiDependees: mutable.Map[Method, EOptionP[Method, TACAI]] = mutable.Map.empty - private val _tacaiDependers: mutable.Map[Method, TACAIDepender] = mutable.Map.empty var _discoveredUnknownTAC: Boolean = false - def registerTacaiDepender(tacEOptP: EOptionP[Method, TACAI], depender: TACAIDepender): Unit = { - _tacaiDependers(tacEOptP.e) = depender - _tacaiDependees(tacEOptP.e) = tacEOptP - } - def getDepender(m: Method): TACAIDepender = _tacaiDependers(m) def updateTacaiDependee(tacEOptP: EOptionP[Method, TACAI]): Unit = _tacaiDependees(tacEOptP.e) = tacEOptP + def getTacaiForContext(callerContext: CallerContext)(implicit ps: PropertyStore): EOptionP[Method, TACAI] = { + val m = callerContext._1.method.definedMethod + + if (_tacaiDependees.contains(m)) { + _tacaiDependees(m) + } else { + val tacEOptP = ps(m, TACAI.key) + _tacaiDependees(tacEOptP.e) = tacEOptP + tacEOptP + } + } + def getTACForContext(callerContext: CallerContext)(implicit ps: PropertyStore): TAC = + getTacaiForContext(callerContext).ub.tac.get // Parameter StringConstancy - private val _entityToParamIndexMapping: mutable.Map[VariableContext, (Int, Method)] = mutable.Map.empty - private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[Method, Seq[VariableContext]]] = + private val _entityToParamIndexMapping: mutable.Map[VariableContext, (Int, DefinedMethod)] = mutable.Map.empty + private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[DefinedMethod, Seq[VariableContext]]] = mutable.Map.empty.withDefaultValue(mutable.Map.empty) private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = mutable.Map.empty - private val _invalidParamReferences: mutable.Map[Method, Set[Int]] = mutable.Map.empty + private val _invalidParamReferences: mutable.Map[Int, Set[Int]] = mutable.Map.empty def registerParameterDependee( index: Int, - m: Method, + dm: DefinedMethod, dependee: EOptionP[VariableContext, StringConstancyProperty] ): Unit = { + if (_entityToParamIndexMapping.contains(dependee.e)) { + throw new IllegalArgumentException(s"Tried to register the same parameter dependee twice: $dependee") + } + + _entityToParamIndexMapping(dependee.e) = (index, dm) if (!_paramIndexToEntityMapping.contains(index)) { _paramIndexToEntityMapping(index) = mutable.Map.empty } - - _paramIndexToEntityMapping(index).updateWith(m) { + _paramIndexToEntityMapping(index).updateWith(dm) { case None => Some(Seq(dependee.e)) - case Some(previous) => Some(previous :+ dependee.e) + case Some(previous) => Some((previous :+ dependee.e).sortBy(_.pc)) } - _entityToParamIndexMapping(dependee.e) = (index, m) - _paramDependees(dependee.e) = dependee + + updateParamDependee(dependee) } - def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = { + def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - } - def registerInvalidParamReference(index: Int, m: Method): Unit = { - _invalidParamReferences.updateWith(m)(ov => Some(ov.getOrElse(Set.empty) + index)) - } + def registerInvalidParamReference(index: Int, dmId: Int): Unit = + _invalidParamReferences.updateWith(dmId) { ov => Some(ov.getOrElse(Set.empty) + index) } def currentSciUB: StringConstancyInformation = { if (_stringDependee.hasUBP) { val paramTrees = if (_discoveredUnknownTAC) { - stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap + parameterIndices.map((_, StringTreeNode.lb)).toMap } else { - stringTree.collectParameterIndices.map { index => + parameterIndices.map { index => val paramOptions = _paramIndexToEntityMapping(index).keys.toSeq - .sortBy(_.fullyQualifiedSignature) - .flatMap { m => - if (_invalidParamReferences.contains(m)) { + .sortBy(_.id) + .flatMap { dm => + if (_invalidParamReferences.contains(dm.id)) { Some(StringTreeNode.ub) } else { - _paramIndexToEntityMapping(index)(m) - .sortBy(_.pc) + _paramIndexToEntityMapping(index)(dm) .map(_paramDependees) .filter(_.hasUBP) .map(_.ub.sci.tree) @@ -118,7 +155,7 @@ private[string] case class ContextStringAnalysisState( def finalSci: StringConstancyInformation = { if (_paramIndexToEntityMapping.valuesIterator.map(_.size).sum == 0) { - val paramTrees = stringTree.collectParameterIndices.map((_, StringTreeNode.lb)).toMap + val paramTrees = _parameterIndices.map((_, StringTreeNode.lb)).toMap StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) } else { currentSciUB @@ -134,12 +171,21 @@ private[string] case class ContextStringAnalysisState( val preliminaryDependees = _tacaiDependees.valuesIterator.filter(_.isRefinable) ++ _paramDependees.valuesIterator.filter(_.isRefinable) ++ - _callersDependee.filter(_.isRefinable) + _callersDependee.filter(_.isRefinable) ++ + Some(_stringDependee).filter(_.isRefinable) - if (_stringDependee.isRefinable) { - preliminaryDependees.toSet + _stringDependee - } else { - preliminaryDependees.toSet - } + preliminaryDependees.toSet + } +} + +object ContextStringAnalysisState { + + def apply( + entity: VariableContext, + stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] + ): ContextStringAnalysisState = { + val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.collectParameterIndices + else Set.empty[Int] + new ContextStringAnalysisState(entity, stringDependee, parameterIndices) } } From ce9f14044695c4ce58206c0d8034a260f0ee6923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 1 Sep 2024 18:03:21 +0200 Subject: [PATCH 520/583] Split up analyses for method parameter contexts --- .../fpcf/analyses/string/StringAnalysis.scala | 126 +++++++++------ .../string/StringAnalysisScheduler.scala | 13 +- .../analyses/string/StringAnalysisState.scala | 152 +++++++++++------- .../tac/fpcf/analyses/string/package.scala | 12 +- 4 files changed, 181 insertions(+), 122 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index d9b6c2ea2f..6832e0826e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -79,20 +79,13 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec */ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { - private implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) - def analyze(vc: VariableContext): ProperPropertyComputationResult = { val vdScp = ps(VariableDefinition(vc.pc, vc.pv, vc.m), StringConstancyProperty.key) implicit val state: ContextStringAnalysisState = ContextStringAnalysisState(vc, vdScp) - if (state.parameterIndices.nonEmpty) { - // We have some parameters that need to be resolved for all callers of this method - val callersEOptP = ps(state.dm, Callers.key) - state.updateCallers(callersEOptP) - - if (callersEOptP.hasUBP) { - handleNewCallers(NoCallers, callersEOptP.ub) - } + state.parameterIndices.foreach { index => + val mpcDependee = ps(MethodParameterContext(index, state.entity.context), StringConstancyProperty.key) + state.registerParameterDependee(mpcDependee) } computeResults @@ -106,6 +99,65 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly handleStringDefinitionUpdate(eps.asInstanceOf[EPS[VariableDefinition, StringConstancyProperty]]) computeResults + // "Upwards" dependency + case EUBP(_: MethodParameterContext, _: StringConstancyProperty) => + state.updateParamDependee(eps.asInstanceOf[EOptionP[MethodParameterContext, StringConstancyProperty]]) + computeResults + + case _ => + throw new IllegalArgumentException(s"Unexpected eps in continuation: $eps") + } + } + + private def handleStringDefinitionUpdate( + newDefinitionEPS: EPS[VariableDefinition, StringConstancyProperty] + )(implicit state: ContextStringAnalysisState): Unit = { + val previousIndices = state.parameterIndices + state.updateStringDependee(newDefinitionEPS) + val newIndices = state.parameterIndices + + newIndices.diff(previousIndices).foreach { index => + val mpcDependee = ps(MethodParameterContext(index, state.entity.context), StringConstancyProperty.key) + state.registerParameterDependee(mpcDependee) + } + } + + private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { + if (state.hasDependees) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty(state.currentSciUB), + state.dependees, + continuation(state) + ) + } else { + Result(state.entity, StringConstancyProperty(state.currentSciUB)) + } + } +} + +private[string] class MethodParameterContextStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { + + private implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) + + def analyze(mpc: MethodParameterContext): ProperPropertyComputationResult = { + implicit val state: MethodParameterContextStringAnalysisState = MethodParameterContextStringAnalysisState(mpc) + + val callersEOptP = ps(state.dm, Callers.key) + state.updateCallers(callersEOptP) + if (callersEOptP.hasUBP) { + handleNewCallers(NoCallers, callersEOptP.ub) + } + + computeResults + } + + private def continuation(state: MethodParameterContextStringAnalysisState)( + eps: SomeEPS + ): ProperPropertyComputationResult = { + implicit val _state: MethodParameterContextStringAnalysisState = state + eps match { case UBP(callers: Callers) => val oldCallers = state._callersDependee.get.ub state.updateCallers(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) @@ -120,47 +172,19 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly } computeResults - // "Upwards" dependency case EUBP(_: VariableContext, _: StringConstancyProperty) => state.updateParamDependee(eps.asInstanceOf[EOptionP[VariableContext, StringConstancyProperty]]) computeResults case _ => - computeResults - } - } - - private def handleStringDefinitionUpdate( - newDefinitionEPS: EPS[VariableDefinition, StringConstancyProperty] - )(implicit state: ContextStringAnalysisState): Unit = { - val previousIndices = state.parameterIndices - state.updateStringDependee(newDefinitionEPS) - val newIndices = state.parameterIndices - - if (newIndices.nonEmpty && newIndices != previousIndices) { - if (previousIndices.isEmpty) { - // We have some parameters that need to be resolved for all callers of this method - val callersEOptP = ps(state.dm, Callers.key) - state.updateCallers(callersEOptP) - if (callersEOptP.hasUBP) { - handleNewCallers(NoCallers, callersEOptP.ub) - } - } else { - for { - (context, callExprOpt) <- state._callerContexts - if callExprOpt.isDefined - index <- newIndices.diff(previousIndices) - } { - handleIndexForCallExpr(context, callExprOpt.get)(index) - } - } + throw new IllegalArgumentException(s"Unexpected eps in continuation: $eps") } } private def handleNewCallers( oldCallers: Callers, newCallers: Callers - )(implicit state: ContextStringAnalysisState): Unit = { + )(implicit state: MethodParameterContextStringAnalysisState): Unit = { val relevantCallerContexts = ListBuffer.empty[(Context, Int)] newCallers.forNewCallerContexts(oldCallers, state.dm) { (_, callerContext, pc, _) => if (callerContext.hasContext && callerContext.method.hasSingleDefinedMethod) { @@ -185,7 +209,7 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly private def handleTACForContext( tac: TAC, context: (Context, Int) - )(implicit state: ContextStringAnalysisState): Unit = { + )(implicit state: MethodParameterContextStringAnalysisState): Unit = { val callExpr = tac.stmts(valueOriginOfPC(context._2, tac.pcToIndex).get) match { case Assignment(_, _, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] case ExprStmt(_, expr) if expr.isInstanceOf[Call[_]] => expr.asInstanceOf[Call[V]] @@ -195,35 +219,37 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly val previousCallExpr = state.addCallExprInformationForContext(context, callExpr) if (previousCallExpr.isEmpty || previousCallExpr.get != callExpr) { - state.parameterIndices.foreach(handleIndexForCallExpr(context, callExpr)) + handleCallExpr(context, callExpr) } } - private def handleIndexForCallExpr( + private def handleCallExpr( callerContext: (Context, Int), callExpr: Call[V] - )(index: Int)(implicit state: ContextStringAnalysisState): Unit = { + )(implicit state: MethodParameterContextStringAnalysisState): Unit = { val dm = callerContext._1.method.asDefinedMethod - if (index >= callExpr.params.size) { + if (state.index >= callExpr.params.size) { OPALLogger.warn( "string analysis", - s"Found parameter reference $index with insufficient parameters during analysis of call: " + s"Found parameter reference ${state.index} with insufficient parameters during analysis of call: " + s"${state.dm.id} in method ID ${dm.id} at PC ${callerContext._2}." ) - state.registerInvalidParamReference(index, dm.id) + state.registerInvalidParamReference(dm.id) } else { val tac = state.getTACForContext(callerContext) val paramVC = VariableContext( callerContext._2, - callExpr.params(index).asVar.toPersistentForm(tac.stmts), + callExpr.params(state.index).asVar.toPersistentForm(tac.stmts), callerContext._1 ) - state.registerParameterDependee(index, dm, ps(paramVC, StringConstancyProperty.key)) + state.registerParameterDependee(dm, ps(paramVC, StringConstancyProperty.key)) } } - private def computeResults(implicit state: ContextStringAnalysisState): ProperPropertyComputationResult = { + private def computeResults(implicit + state: MethodParameterContextStringAnalysisState + ): ProperPropertyComputationResult = { if (state.hasDependees) { InterimResult( state.entity, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala index d03a93e653..7a0351f049 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala @@ -30,11 +30,13 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(MethodStringFlow)) - override final type InitializationData = (ContextFreeStringAnalysis, ContextStringAnalysis) + override final type InitializationData = + (ContextFreeStringAnalysis, ContextStringAnalysis, MethodParameterContextStringAnalysis) override def init(p: SomeProject, ps: PropertyStore): InitializationData = ( new ContextFreeStringAnalysis(p), - new ContextStringAnalysis(p) + new ContextStringAnalysis(p), + new MethodParameterContextStringAnalysis(p) ) override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -52,9 +54,10 @@ object LazyStringAnalysis StringConstancyProperty.key, (entity: Entity) => { entity match { - case vd: VariableDefinition => data._1.analyze(vd) - case vc: VariableContext => data._2.analyze(vc) - case e => throw new IllegalArgumentException(s"Cannot process entity $e") + case vd: VariableDefinition => data._1.analyze(vd) + case vc: VariableContext => data._2.analyze(vc) + case vc: MethodParameterContext => data._3.analyze(vc) + case e => throw new IllegalArgumentException(s"Cannot process entity $e") } } ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 44ac29bf26..30b809984d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -51,15 +51,77 @@ private[string] class ContextStringAnalysisState private ( private def stringTree: StringTreeNode = _stringDependee.ub.sci.tree def parameterIndices: Set[Int] = _parameterIndices + // Parameter StringConstancy + private val _paramDependees: mutable.Map[MethodParameterContext, EOptionP[ + MethodParameterContext, + StringConstancyProperty + ]] = + mutable.Map.empty + + def registerParameterDependee(dependee: EOptionP[MethodParameterContext, StringConstancyProperty]): Unit = { + if (_paramDependees.contains(dependee.e)) { + throw new IllegalArgumentException(s"Tried to register the same parameter dependee twice: $dependee") + } + + updateParamDependee(dependee) + } + def updateParamDependee(dependee: EOptionP[MethodParameterContext, StringConstancyProperty]): Unit = + _paramDependees(dependee.e) = dependee + + def currentSciUB: StringConstancyInformation = { + if (_stringDependee.hasUBP) { + val paramTrees = _paramDependees.map { kv => + ( + kv._1.index, + if (kv._2.hasUBP) kv._2.ub.sci.tree + else StringTreeNode.ub + ) + }.toMap + + StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) + } else { + StringConstancyInformation.ub + } + } + + def hasDependees: Boolean = _stringDependee.isRefinable || _paramDependees.valuesIterator.exists(_.isRefinable) + + def dependees: Set[EOptionP[Entity, Property]] = { + val preliminaryDependees = + _paramDependees.valuesIterator.filter(_.isRefinable) ++ + Some(_stringDependee).filter(_.isRefinable) + + preliminaryDependees.toSet + } +} + +object ContextStringAnalysisState { + + def apply( + entity: VariableContext, + stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] + ): ContextStringAnalysisState = { + val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.collectParameterIndices + else Set.empty[Int] + new ContextStringAnalysisState(entity, stringDependee, parameterIndices) + } +} + +private[string] case class MethodParameterContextStringAnalysisState( + private val _entity: MethodParameterContext +) { + + def entity: MethodParameterContext = _entity + def dm: DeclaredMethod = _entity.context.method + def index: Int = _entity.index + // Callers private[string] type CallerContext = (Context, Int) var _callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = None - val _callerContexts: mutable.Map[CallerContext, Option[Call[V]]] = mutable.Map.empty + private val _callerContexts: mutable.Map[CallerContext, Option[Call[V]]] = mutable.Map.empty private val _callerContextsByMethod: mutable.Map[Method, Seq[CallerContext]] = mutable.Map.empty - def updateCallers(newCallers: EOptionP[DeclaredMethod, Callers]): Unit = { - _callersDependee = Some(newCallers) - } + def updateCallers(newCallers: EOptionP[DeclaredMethod, Callers]): Unit = _callersDependee = Some(newCallers) def addCallerContext(callerContext: CallerContext): Unit = { _callerContexts.update(callerContext, None) @@ -92,27 +154,20 @@ private[string] class ContextStringAnalysisState private ( getTacaiForContext(callerContext).ub.tac.get // Parameter StringConstancy - private val _entityToParamIndexMapping: mutable.Map[VariableContext, (Int, DefinedMethod)] = mutable.Map.empty - private val _paramIndexToEntityMapping: mutable.Map[Int, mutable.Map[DefinedMethod, Seq[VariableContext]]] = - mutable.Map.empty.withDefaultValue(mutable.Map.empty) + private val _methodToEntityMapping: mutable.Map[DefinedMethod, Seq[VariableContext]] = mutable.Map.empty private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = mutable.Map.empty - private val _invalidParamReferences: mutable.Map[Int, Set[Int]] = mutable.Map.empty + private var _methodsWithInvalidParamReferences: Set[Int] = Set.empty[Int] def registerParameterDependee( - index: Int, dm: DefinedMethod, dependee: EOptionP[VariableContext, StringConstancyProperty] ): Unit = { - if (_entityToParamIndexMapping.contains(dependee.e)) { + if (_paramDependees.contains(dependee.e)) { throw new IllegalArgumentException(s"Tried to register the same parameter dependee twice: $dependee") } - _entityToParamIndexMapping(dependee.e) = (index, dm) - if (!_paramIndexToEntityMapping.contains(index)) { - _paramIndexToEntityMapping(index) = mutable.Map.empty - } - _paramIndexToEntityMapping(index).updateWith(dm) { + _methodToEntityMapping.updateWith(dm) { case None => Some(Seq(dependee.e)) case Some(previous) => Some((previous :+ dependee.e).sortBy(_.pc)) } @@ -121,49 +176,39 @@ private[string] class ContextStringAnalysisState private ( } def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def registerInvalidParamReference(index: Int, dmId: Int): Unit = - _invalidParamReferences.updateWith(dmId) { ov => Some(ov.getOrElse(Set.empty) + index) } + def registerInvalidParamReference(dmId: Int): Unit = + _methodsWithInvalidParamReferences += dmId def currentSciUB: StringConstancyInformation = { - if (_stringDependee.hasUBP) { - val paramTrees = if (_discoveredUnknownTAC) { - parameterIndices.map((_, StringTreeNode.lb)).toMap - } else { - parameterIndices.map { index => - val paramOptions = _paramIndexToEntityMapping(index).keys.toSeq - .sortBy(_.id) - .flatMap { dm => - if (_invalidParamReferences.contains(dm.id)) { - Some(StringTreeNode.ub) - } else { - _paramIndexToEntityMapping(index)(dm) - .map(_paramDependees) - .filter(_.hasUBP) - .map(_.ub.sci.tree) - } - } - - (index, StringTreeOr(paramOptions).simplify) - }.toMap - } - - StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) + if (_discoveredUnknownTAC) { + StringConstancyInformation(StringTreeNode.lb) } else { - StringConstancyInformation.ub + val paramOptions = _methodToEntityMapping.keys.toSeq + .sortBy(_.id) + .flatMap { dm => + if (_methodsWithInvalidParamReferences.contains(dm.id)) { + Some(StringTreeNode.ub) + } else { + _methodToEntityMapping(dm) + .map(_paramDependees) + .filter(_.hasUBP) + .map(_.ub.sci.tree) + } + } + + StringConstancyInformation(StringTreeOr(paramOptions).simplify) } } def finalSci: StringConstancyInformation = { - if (_paramIndexToEntityMapping.valuesIterator.map(_.size).sum == 0) { - val paramTrees = _parameterIndices.map((_, StringTreeNode.lb)).toMap - StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) + if (_methodToEntityMapping.isEmpty) { + StringConstancyInformation(StringTreeNode.lb) } else { currentSciUB } } - def hasDependees: Boolean = _stringDependee.isRefinable || - _callersDependee.exists(_.isRefinable) || + def hasDependees: Boolean = _callersDependee.exists(_.isRefinable) || _tacaiDependees.valuesIterator.exists(_.isRefinable) || _paramDependees.valuesIterator.exists(_.isRefinable) @@ -171,21 +216,8 @@ private[string] class ContextStringAnalysisState private ( val preliminaryDependees = _tacaiDependees.valuesIterator.filter(_.isRefinable) ++ _paramDependees.valuesIterator.filter(_.isRefinable) ++ - _callersDependee.filter(_.isRefinable) ++ - Some(_stringDependee).filter(_.isRefinable) + _callersDependee.filter(_.isRefinable) preliminaryDependees.toSet } } - -object ContextStringAnalysisState { - - def apply( - entity: VariableContext, - stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] - ): ContextStringAnalysisState = { - val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.collectParameterIndices - else Set.empty[Int] - new ContextStringAnalysisState(entity, stringDependee, parameterIndices) - } -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index aa8fea5c48..d55bdc5ce6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -15,15 +15,13 @@ package object string { type TAC = TACode[TACMethodParameter, V] - trait AnalyzableVariable { - def pc: Int - def pv: PV - def m: Method + private[string] case class VariableDefinition(pc: Int, pv: PV, m: Method) + case class VariableContext(pc: Int, pv: PV, context: Context) { + def m: Method = context.method.definedMethod } - private[string] case class VariableDefinition(pc: Int, pv: PV, m: Method) extends AnalyzableVariable - case class VariableContext(pc: Int, pv: PV, context: Context) extends AnalyzableVariable { - override def m: Method = context.method.definedMethod + private[string] case class MethodParameterContext(index: Int, context: Context) { + def m: Method = context.method.definedMethod } case class MethodPC(pc: Int, dm: DefinedMethod) From 4a122dc0cdb423728045a52b19c9232f7b3c3e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:26:59 +0200 Subject: [PATCH 521/583] Introduce node hashcode caching for flow graph nodes --- .../fpcf/analyses/string/flowanalysis/FlowGraphNode.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index 0a86b36b0b..c2110e86ae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -31,6 +31,11 @@ case class Region(regionType: RegionType, override val nodeIds: Set[Int], entry: override def toString: String = s"Region(${regionType.productPrefix}; ${nodeIds.toList.sorted.mkString(",")}; ${entry.toString})" + + // Performance optimizations + private lazy val _hashCode = scala.util.hashing.MurmurHash3.productHash(this) + override def hashCode(): Int = _hashCode + override def canEqual(obj: Any): Boolean = obj.hashCode() == _hashCode } case class Statement(nodeId: Int) extends FlowGraphNode { From 75ef400c36ed0fbf06191df5111f3dce40f11b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:28:23 +0200 Subject: [PATCH 522/583] Optimize parameter replacement in string trees --- .../properties/string/StringTreeNode.scala | 20 +++++++++++-------- .../analyses/string/StringAnalysisState.scala | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index d052504a57..e74e8aca71 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -5,7 +5,6 @@ package fpcf package properties package string -import scala.collection.immutable.Seq import scala.util.Try import scala.util.matching.Regex @@ -32,10 +31,15 @@ sealed trait StringTreeNode { def constancyLevel: StringConstancyLevel.Value - def collectParameterIndices: Set[Int] = children.flatMap(_.collectParameterIndices).toSet + lazy val parameterIndices: Set[Int] = children.flatMap(_.parameterIndices).toSet final def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { - if (parameters.isEmpty) this - else _replaceParameters(parameters) + if (parameters.isEmpty || + parameterIndices.isEmpty || + parameters.keySet.intersect(parameterIndices).isEmpty + ) + this + else + _replaceParameters(parameters) } protected def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode @@ -96,7 +100,7 @@ case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNod override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC - override def collectParameterIndices: Set[Int] = child.collectParameterIndices + override lazy val parameterIndices: Set[Int] = child.parameterIndices def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = StringTreeRepetition(child.replaceParameters(parameters)) @@ -179,7 +183,7 @@ trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { override def constancyLevel: StringConstancyLevel.Value = _children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) - override def collectParameterIndices: Set[Int] = _children.flatMap(_.collectParameterIndices).toSet + override lazy val parameterIndices: Set[Int] = _children.flatMap(_.parameterIndices).toSet } object StringTreeOr { @@ -199,7 +203,7 @@ object StringTreeOr { def fromNodes(children: StringTreeNode*): StringTreeNode = SetBasedStringTreeOr.createWithSimplify(children.toSet) } -case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNode]) extends StringTreeOr { +private case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNode]) extends StringTreeOr { override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.map(_.sorted).sortBy(_.toRegex)) @@ -330,7 +334,7 @@ object StringTreeEmptyConst extends StringTreeConst("") { case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { override def _toRegex: String = ".*" - override def collectParameterIndices: Set[Int] = Set(index) + override lazy val parameterIndices: Set[Int] = Set(index) override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = parameters.getOrElse(index, this) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 30b809984d..80c1f689b8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -46,7 +46,7 @@ private[string] class ContextStringAnalysisState private ( def updateStringDependee(stringDependee: EPS[VariableDefinition, StringConstancyProperty]): Unit = { _stringDependee = stringDependee // TODO parameter indices should always grow <- when the new string dependee only contains additional information - _parameterIndices ++= stringDependee.ub.sci.tree.collectParameterIndices + _parameterIndices ++= stringDependee.ub.sci.tree.parameterIndices } private def stringTree: StringTreeNode = _stringDependee.ub.sci.tree def parameterIndices: Set[Int] = _parameterIndices @@ -101,7 +101,7 @@ object ContextStringAnalysisState { entity: VariableContext, stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] ): ContextStringAnalysisState = { - val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.collectParameterIndices + val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.parameterIndices else Set.empty[Int] new ContextStringAnalysisState(entity, stringDependee, parameterIndices) } From b80f551302d177fe7258ad4fcb2f39e3827cb23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:28:55 +0200 Subject: [PATCH 523/583] Optimize children mapping in string tree OR nodes --- .../br/fpcf/properties/string/StringTreeNode.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index e74e8aca71..bc080517ff 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -170,7 +170,7 @@ trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { protected val _children: Iterable[StringTreeNode] - override final val children: Seq[StringTreeNode] = _children.toSeq + override final lazy val children: Seq[StringTreeNode] = _children.toSeq override def _toRegex: String = { children.size match { @@ -294,10 +294,13 @@ object SetBasedStringTreeOr { case 0 => StringTreeInvalidElement case 1 => _children.head case _ => - val newChildren = _children.flatMap { - case orChild: StringTreeOr => orChild.children - case child => Set(child) + val newChildrenBuilder = Set.newBuilder[StringTreeNode] + _children.foreach { + case setOrChild: SetBasedStringTreeOr => newChildrenBuilder.addAll(setOrChild._children) + case orChild: StringTreeOr => newChildrenBuilder.addAll(orChild.children) + case child => newChildrenBuilder.addOne(child) } + val newChildren = newChildrenBuilder.result() newChildren.size match { case 1 => newChildren.head case _ => SetBasedStringTreeOr(newChildren) From 010c26c420c31c024915cfe692edf10f56ccf81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:30:20 +0200 Subject: [PATCH 524/583] Limit size of string trees by configurable maximum depth --- .../properties/string/StringTreeNode.scala | 2 + .../fpcf/analyses/string/StringAnalysis.scala | 42 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index bc080517ff..f87b2dcee6 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -15,6 +15,8 @@ sealed trait StringTreeNode { val children: Seq[StringTreeNode] + lazy val depth: Int = children.map(_.depth).maxOption.getOrElse(0) + 1 + def sorted: StringTreeNode private var _regex: Option[String] = None diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 6832e0826e..d3d258ee4a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -27,14 +27,41 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP +import org.opalj.log.Error +import org.opalj.log.Info import org.opalj.log.OPALLogger +import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow +trait StringAnalysis extends FPCFAnalysis { + + private final val ConfigLogCategory = "analysis configuration - string analysis" + + protected val maxDepth: Int = { + val maxDepth = + try { + project.config.getInt(StringAnalysis.MaxDepthConfigKey) + } catch { + case t: Throwable => + logOnce(Error(ConfigLogCategory, s"couldn't read: ${StringAnalysis.MaxDepthConfigKey}", t)) + 30 + } + + logOnce(Info(ConfigLogCategory, "using maximum depth " + maxDepth)) + maxDepth + } +} + +object StringAnalysis { + + final val MaxDepthConfigKey = "org.opalj.fpcf.analyses.string.StringAnalysis.maxDepth" +} + /** * @author Maximilian Rüsch */ -private[string] class ContextFreeStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { +private[string] class ContextFreeStringAnalysis(override val project: SomeProject) extends StringAnalysis { def analyze(vd: VariableDefinition): ProperPropertyComputationResult = computeResults(ContextFreeStringAnalysisState(vd, ps(vd.m, MethodStringFlow.key))) @@ -67,7 +94,14 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec private def computeNewUpperBound(state: ContextFreeStringAnalysisState): StringConstancyProperty = { StringConstancyProperty(state.stringFlowDependee match { case UBP(methodStringFlow) => - StringConstancyInformation(methodStringFlow(state.entity.pc, state.entity.pv).simplify) + val tree = methodStringFlow(state.entity.pc, state.entity.pv) + if (tree.depth > maxDepth) { + // String constancy information got too complex, abort. This guard can probably be removed once + // recursing functions are properly handled using e.g. the widen-converge approach. + StringConstancyInformation.lb + } else { + StringConstancyInformation(tree.simplify) + } case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub }) @@ -77,7 +111,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec /** * @author Maximilian Rüsch */ -class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { +class ContextStringAnalysis(override val project: SomeProject) extends StringAnalysis { def analyze(vc: VariableContext): ProperPropertyComputationResult = { val vdScp = ps(VariableDefinition(vc.pc, vc.pv, vc.m), StringConstancyProperty.key) @@ -137,7 +171,7 @@ class ContextStringAnalysis(override val project: SomeProject) extends FPCFAnaly } } -private[string] class MethodParameterContextStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { +private[string] class MethodParameterContextStringAnalysis(override val project: SomeProject) extends StringAnalysis { private implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) From 4c9fac49f755114fe3fbd8e7da5dd7897585c871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:31:22 +0200 Subject: [PATCH 525/583] Optimize handling of string environments during data flow analysis --- .../flowanalysis/DataFlowAnalysis.scala | 2 +- .../string/StringTreeEnvironment.scala | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 277af0c48c..87c1c5298b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -113,7 +113,7 @@ class DataFlowAnalysis( val previousEnvs = currentNode.diPredecessors.toList.map { dp => pipe(currentNode.outer, currentNodeEnvs(dp)) } - currentNodeEnvs.update(currentNode, previousEnvs.head.joinMany(previousEnvs.tail)) + currentNodeEnvs.update(currentNode, StringTreeEnvironment.joinMany(previousEnvs)) } currentNodeEnvs(sortedNodes.last.asInstanceOf[g.NodeT]) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index b40e9fd630..2760969989 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -14,16 +14,16 @@ import org.opalj.br.fpcf.properties.string.StringTreeOr */ case class StringTreeEnvironment private ( private val map: Map[PDUWeb, StringTreeNode], - private val pcToWebs: Map[Int, Iterable[PDUWeb]] + private val pcToWebs: Map[Int, Seq[PDUWeb]] ) { private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = { val webs = pv match { - case PDVar(_, _) => pcToWebs(pc) - case PUVar(_, varDefPCs) => varDefPCs.map(pcToWebs).flatten + case _: PDVar[_] => pcToWebs(pc) + case _: PUVar[_] => pv.defPCs.map(pcToWebs).toSeq.flatten } - webs.toSeq.sortBy(_.defPCs.toList.toSet.min) + webs.sortBy(_.defPCs.toList.toSet.min) } private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys @@ -67,18 +67,6 @@ case class StringTreeEnvironment private ( recreate(map.transform { (web, tree) => SetBasedStringTreeOr.createWithSimplify(Set(tree, other.map(web))) }) } - def joinMany(others: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { - if (others.isEmpty) { - this - } else { - // This only works as long as environment maps are not sparse - recreate(map.transform { (web, tree) => - val otherValues = others.map(_.map(web)).concat(Iterable(tree)) - SetBasedStringTreeOr.createWithSimplify(otherValues.toSet) - }) - } - } - def recreate(newMap: Map[PDUWeb, StringTreeNode]): StringTreeEnvironment = StringTreeEnvironment(newMap, pcToWebs) } @@ -86,8 +74,21 @@ object StringTreeEnvironment { def apply(map: Map[PDUWeb, StringTreeNode]): StringTreeEnvironment = { val pcToWebs = - map.keys.flatMap(web => web.defPCs.map((_, web))).groupMap(_._1)(_._2) - .withDefaultValue(Iterable.empty) + map.keys.toSeq.flatMap(web => web.defPCs.map((_, web))).groupMap(_._1)(_._2) + .withDefaultValue(Seq.empty) StringTreeEnvironment(map, pcToWebs) } + + def joinMany(envs: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { + envs.size match { + case 0 => throw new IllegalArgumentException("Cannot join zero environments!") + case 1 => envs.head + case _ => + val head = envs.head + // This only works as long as environment maps are not sparse + head.recreate(head.map.transform { (web, _) => + SetBasedStringTreeOr.createWithSimplify(envs.map(_.map(web)).toSet) + }) + } + } } From 9a1f1663d8e6e55a5589a59647037e2c52d44963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:32:47 +0200 Subject: [PATCH 526/583] Fix loading domain into test framework --- .../fpcf/fixtures/string_analysis/FunctionCalls.java | 11 ++--------- .../scala/org/opalj/fpcf/StringAnalysisTest.scala | 6 ++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java index aab59ee23c..dab1197f1c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -112,16 +112,9 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { /** * A case where the single valid return value of the called function can be resolved without calling the function. */ - @Constant(n = 0, levels = Level.TRUTH, domains = DomainLevel.L1, value = "(One|java.lang.Object|val)") + @Constant(n = 0, levels = Level.TRUTH, value = "val") @Failure(n = 0, levels = { Level.L0, Level.L1 }, domains = DomainLevel.L1) - // Since the virtual function return value is inlined in L2 and its actual runtime return - // value is not used, the function call gets converted to a method call, which modifies the - // TAC: The def PC from the `analyzeString` parameter is now different and points to the def - // PC for the `resolvableReturnValueFunction` parameter. This results in no string flow being - // detected since the def and use sites are now inconsistent. - // The actual truth @Constant(n = 0, value = "val", domains = DomainLevel.L2) - @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2) - @Invalid(n = 0, levels = Level.L2, domains = DomainLevel.L2) + @Constant(n = 0, levels = Level.L2, domains = DomainLevel.L1, value = "(One|java.lang.Object|val)") public void resolvableReturnValue() { analyzeString(resolvableReturnValueFunction("val", 42)); } 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 55b163d560..fd7d317cb9 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 @@ -7,6 +7,8 @@ import java.net.URL import com.typesafe.config.Config import com.typesafe.config.ConfigValueFactory +import org.opalj.ai.Domain +import org.opalj.ai.domain.RecordDefUse import org.opalj.ai.domain.l1.DefaultDomainWithCFGAndDefUse import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse import org.opalj.ai.fpcf.properties.AIDomainFactoryKey @@ -85,6 +87,10 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { case None => Set(domain) case Some(requirements) => requirements + domain } + p.updateProjectInformationKeyInitializationData(EagerDetachedTACAIKey) { + case None => m => domain.getConstructors.head.newInstance(p, m).asInstanceOf[Domain with RecordDefUse] + case Some(_) => m => domain.getConstructors.head.newInstance(p, m).asInstanceOf[Domain with RecordDefUse] + } initBeforeCallGraph(p) From b2d2b0b039b1dbe13e0ace1f53d27fc4c8439ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:36:26 +0200 Subject: [PATCH 527/583] Make excluded packages configurable --- .../string/MethodStringFlowAnalysis.scala | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 29fd6762de..942ab7e4fd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -5,6 +5,8 @@ package fpcf package analyses package string +import scala.jdk.CollectionConverters._ + import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey @@ -18,6 +20,9 @@ import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement @@ -31,12 +36,36 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis { + private final val ConfigLogCategory = "analysis configuration - method string flow analysis" + + private val excludedPackages: Seq[String] = { + val packages = + try { + project.config.getStringList(MethodStringFlowAnalysis.ExcludedPackagesConfigKey).asScala + } catch { + case t: Throwable => + logOnce { + Error( + ConfigLogCategory, + s"couldn't read: ${MethodStringFlowAnalysis.ExcludedPackagesConfigKey}", + t + ) + } + Seq.empty[String] + } + + logOnce(Info(ConfigLogCategory, s"${packages.size} packages are excluded from string flow analysis")) + packages.toSeq + } + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(method: Method): ProperPropertyComputationResult = { val state = ComputationState(method, declaredMethods(method), ps(method, TACAI.key)) - if (state.tacDependee.isRefinable) { + if (excludedPackages.exists(method.classFile.thisType.packageName.startsWith(_))) { + Result(state.entity, MethodStringFlow.lb) + } else if (state.tacDependee.isRefinable) { InterimResult.forUB( state.entity, MethodStringFlow.ub, @@ -109,3 +138,8 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn MethodStringFlow(state.flowAnalysis.compute(state.getFlowFunctionsByPC)(startEnv)) } } + +object MethodStringFlowAnalysis { + + final val ExcludedPackagesConfigKey = "org.opalj.fpcf.analyses.string.MethodStringFlowAnalysis.excludedPackages" +} From a352518dc63fe39dcceab5d77868c098af896e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:38:14 +0200 Subject: [PATCH 528/583] Introduce soundness mode for data flow analysis --- .../fixtures/string_analysis/External.java | 4 +- .../fpcf/fixtures/string_analysis/Loops.java | 62 +++++++++++++------ .../string/MethodStringFlowAnalysis.scala | 20 +++++- .../flowanalysis/DataFlowAnalysis.scala | 23 +++---- 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index cb9b85a1ea..78e940da32 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -127,10 +127,8 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { * Methods are called that return a string but are not within this project => cannot / will not interpret */ @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") - @Failure(n = 0, levels = Level.L0) - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = ".*") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) - @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = ".*") @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") public void methodsOutOfScopeTest() throws FileNotFoundException { File file = new File("my-file.txt"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java index d2a92a3612..1f0d3ecf4d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -21,9 +21,13 @@ public void analyzeString(String s) {} * Simple for loops with known and unknown bounds. Note that no analysis supports loops yet. */ @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") - @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "a(b)*") - @Dynamic(n = 1, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); for (int i = 0; i < 10; i++) { @@ -40,8 +44,9 @@ public void simpleForLoopWithKnownBounds() { } @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "((x|^-?\\d+$))*yz") - @Dynamic(n = 0, levels = Level.L0, value = ".*") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = "(.*|.*yz)") + @Failure(n = 0, levels = Level.L0) + @PartiallyConstant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(^-?\\d+$|x)yz") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 20; i++) { @@ -57,7 +62,9 @@ public void ifElseInLoopWithAppendAfterwards() { } @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") - @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") public void nestedLoops(int range) { for (int i = 0; i < range; i++) { StringBuilder sb = new StringBuilder("a"); @@ -69,8 +76,9 @@ public void nestedLoops(int range) { } @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) - @Dynamic(n = 0, levels = Level.L0, value = ".*") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, value = "(.*|.*yz)") + @Failure(n = 0, levels = Level.L0) + @PartiallyConstant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(^-?\\d+$|x)yz") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void stringBufferExample() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 20; i++) { @@ -86,7 +94,9 @@ public void stringBufferExample() { } @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") public void whileTrueWithBreak() { StringBuilder sb = new StringBuilder("a"); while (true) { @@ -99,7 +109,9 @@ public void whileTrueWithBreak() { } @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") public void whileNonTrueWithBreak(int i) { StringBuilder sb = new StringBuilder("a"); int j = 0; @@ -114,17 +126,17 @@ public void whileNonTrueWithBreak(int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(iv1|iv2): ") - @Dynamic(n = 0, levels = Level.L0, value = ".*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(iv1|iv2): ") // The real value is not fully resolved yet, since the string builder is used in a while loop, // which leads to the string builder potentially carrying any value. This can be refined by // recording pc specific states during data flow analysis. - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "((iv1|iv2): |.*)") @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?") - @Dynamic(n = 1, levels = Level.L0, value = ".*") - @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = ".*") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(iv1|iv2): great!") @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|.*.*)") - @Dynamic(n = 1, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(.*|.*java.lang.Runtime)") + @Constant(n = 1, levels = Level.L2, soundness = SoundnessMode.LOW, value = "((iv1|iv2): great!|(iv1|iv2): great!java.lang.Runtime)") @Dynamic(n = 1, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") public void extensiveWithManyControlStructures(boolean cond) { StringBuilder sb = new StringBuilder(); @@ -154,12 +166,18 @@ public void extensiveWithManyControlStructures(boolean cond) { // The bytecode produces an "if" within an "if" inside the first loop => two conditions @Constant(n = 0, levels = Level.TRUTH, value = "abc((d)?)*") - @Dynamic(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(abc|abcd)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") @Constant(n = 1, levels = Level.TRUTH, value = "") - @Dynamic(n = 1, levels = Level.L0, value = ".*") - @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, value = "(|.*)") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "") + @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(|.*)") @Dynamic(n = 2, levels = Level.TRUTH, value = "((.*)?)*") - @Dynamic(n = 2, levels = { Level.L0, Level.L1, Level.L2 }, value = ".*") + @Failure(n = 2, levels = Level.L0) + @Constant(n = 2, levels = Level.L1, soundness = SoundnessMode.LOW, value = "") + @Constant(n = 2, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(|java.lang.Runtime)") + @Dynamic(n = 2, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") public void breakContinueExamples(int value) { StringBuilder sb1 = new StringBuilder("abc"); for (int i = 0; i < value; i++) { @@ -199,8 +217,12 @@ public void breakContinueExamples(int value) { /** * Some comprehensive example for experimental purposes taken from the JDK and slightly modified */ - @Constant(n = 0, value = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?", levels = Level.TRUTH) - @Dynamic(n = 0, value = ".*", levels = { Level.L0, Level.L1, Level.L2 }) + @Constant(n = 0, levels = Level.TRUTH, value = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "Hello: ") + @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, + value = "(Hello: |Hello: StringBuilder|Hello: java.lang.Runtime|Hello: java.lang.StringBuilder)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") protected void setDebugFlags(String[] var1) { for(int var2 = 0; var2 < var1.length; ++var2) { String var3 = var1[var2]; diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 942ab7e4fd..5fcdbfd928 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -27,6 +27,7 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -58,6 +59,22 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn packages.toSeq } + private val soundnessMode: SoundnessMode = { + val mode = + try { + SoundnessMode(project.config.getBoolean(MethodStringFlowAnalysis.SoundnessModeConfigKey)) + } catch { + case t: Throwable => + logOnce { + Error(ConfigLogCategory, s"couldn't read: ${MethodStringFlowAnalysis.SoundnessModeConfigKey}", t) + } + SoundnessMode(false) + } + + logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) + mode + } + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(method: Method): ProperPropertyComputationResult = { @@ -93,7 +110,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entry) state.superFlowGraph = superFlowGraph state.controlTree = controlTree - state.flowAnalysis = new DataFlowAnalysis(state.controlTree, state.superFlowGraph) + state.flowAnalysis = new DataFlowAnalysis(state.controlTree, state.superFlowGraph, soundnessMode) state.flowGraph.nodes.toOuter.foreach { case Statement(pc) if pc >= 0 => @@ -142,4 +159,5 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn object MethodStringFlowAnalysis { final val ExcludedPackagesConfigKey = "org.opalj.fpcf.analyses.string.MethodStringFlowAnalysis.excludedPackages" + final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.MethodStringFlowAnalysis.highSoundness" } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 87c1c5298b..4f99898125 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -9,6 +9,8 @@ package flowanalysis import scala.collection.mutable import org.opalj.br.fpcf.properties.string.StringTreeDynamicString +import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement +import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment @@ -17,7 +19,8 @@ import scalax.collection.GraphTraversal.Parameters class DataFlowAnalysis( private val controlTree: ControlTree, - private val superFlowGraph: SuperFlowGraph + private val superFlowGraph: SuperFlowGraph, + private val soundnessMode: SoundnessMode ) { private val _nodeOrderings = mutable.Map.empty[FlowGraphNode, Seq[SuperFlowGraph#NodeT]] @@ -125,9 +128,8 @@ class DataFlowAnalysis( def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { val resultEnv = pipe(entry, env) - // Looped operations that modify environment contents are not supported here - if (resultEnv != env) env.updateAll(StringTreeDynamicString) - else env + if (resultEnv != env && soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + else resultEnv } def processWhileLoop(entry: FlowGraphNode): StringTreeEnvironment = { @@ -145,9 +147,8 @@ class DataFlowAnalysis( resultEnv = pipe(currentNode.outer, resultEnv) } - // Looped operations that modify environment contents are not supported here - if (resultEnv != envAfterEntry) envAfterEntry.updateAll(StringTreeDynamicString) - else envAfterEntry + if (resultEnv != envAfterEntry && soundnessMode.isHigh) envAfterEntry.updateAll(StringTreeDynamicString) + else resultEnv } def processNaturalLoop(entry: FlowGraphNode): StringTreeEnvironment = { @@ -165,7 +166,8 @@ class DataFlowAnalysis( ) if (isCyclic) { - env.updateAll(StringTreeDynamicString) + if (soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + else env.updateAll(StringTreeInvalidElement) } else { // Handle resulting acyclic region val resultEnv = handleProperSubregion[FlowGraphNode, removedBackEdgesGraph.type]( @@ -173,9 +175,8 @@ class DataFlowAnalysis( removedBackEdgesGraph.nodes.toSet, entry ) - // Looped operations that modify string contents are not supported here - if (resultEnv != env) env.updateAll(StringTreeDynamicString) - else env + if (resultEnv != env && soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + else resultEnv } } From 7ef3a13077b8baea5a090630bd6e88f731598bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 10:38:49 +0200 Subject: [PATCH 529/583] Fill in missing config values into string analysis and reference conf --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 9 +++++++++ OPAL/tac/src/main/resources/reference.conf | 10 ++++++++++ 2 files changed, 19 insertions(+) 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 fd7d317cb9..bb3209ee75 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 @@ -3,6 +3,7 @@ package org.opalj package fpcf import java.net.URL +import java.util import com.typesafe.config.Config import com.typesafe.config.ConfigValueFactory @@ -48,6 +49,8 @@ import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis +import org.opalj.tac.fpcf.analyses.string.MethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.StringAnalysis import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis @@ -73,6 +76,12 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { super.createConfig() .withValue(InterpretationHandler.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue( + MethodStringFlowAnalysis.ExcludedPackagesConfigKey, + ConfigValueFactory.fromIterable(new util.ArrayList[String]()) + ) + .withValue(MethodStringFlowAnalysis.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue(StringAnalysis.MaxDepthConfigKey, ConfigValueFactory.fromAnyRef(30)) } override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string_analysis") diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index cc240c1552..c8a50efce0 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1496,6 +1496,16 @@ org.opalj { fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, string { InterpretationHandler.highSoundness = false, + MethodStringFlowAnalysis { + excludedPackages = [ + "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets + ], + highSoundness = false + }, + StringAnalysis { + highSoundness = false, + maxDepth = 30 + }, l1.L1StringAnalysis.fieldWriteThreshold = 100 } } From 951b950a2b9f36a2f7b85a9f31d5ff9da1aea91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 11:14:04 +0200 Subject: [PATCH 530/583] Fix invalid parameter references due to cross-method field read string trees --- .../fixtures/string_analysis/External.java | 1 + .../fpcf/analyses/string/StringAnalysis.scala | 25 ++++++------------- .../L2FieldReadInterpreter.scala | 18 ++++++++----- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index 78e940da32..928ff58c4f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -79,6 +79,7 @@ public void fieldInitByConstructorRead() { @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.LOW) @Dynamic(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.HIGH, value = "^-?\\d*\\.{0,1}\\d+$", reason = "the field value is inlined using L2 domains") + @Invalid(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW) public void fieldInitByConstructorParameter() { analyzeString(new StringBuilder().append(fieldWithConstructorParameterInit).toString()); } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index d3d258ee4a..a9a5a819ee 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -29,7 +29,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.log.Error import org.opalj.log.Info -import org.opalj.log.OPALLogger import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow @@ -262,23 +261,13 @@ private[string] class MethodParameterContextStringAnalysis(override val project: callExpr: Call[V] )(implicit state: MethodParameterContextStringAnalysisState): Unit = { val dm = callerContext._1.method.asDefinedMethod - if (state.index >= callExpr.params.size) { - OPALLogger.warn( - "string analysis", - s"Found parameter reference ${state.index} with insufficient parameters during analysis of call: " - + s"${state.dm.id} in method ID ${dm.id} at PC ${callerContext._2}." - ) - state.registerInvalidParamReference(dm.id) - } else { - val tac = state.getTACForContext(callerContext) - val paramVC = VariableContext( - callerContext._2, - callExpr.params(state.index).asVar.toPersistentForm(tac.stmts), - callerContext._1 - ) - - state.registerParameterDependee(dm, ps(paramVC, StringConstancyProperty.key)) - } + val tac = state.getTACForContext(callerContext) + val paramVC = VariableContext( + callerContext._2, + callExpr.params(state.index).asVar.toPersistentForm(tac.stmts), + callerContext._1 + ) + state.registerParameterDependee(dm, ps(paramVC, StringConstancyProperty.key)) } private def computeResults(implicit diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index 6cdf65ca31..bfe48fed45 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -125,8 +125,6 @@ class L2FieldReadInterpreter( implicit val accessState: FieldReadState = FieldReadState(target, fieldAccessEOptP) if (fieldAccessEOptP.hasUBP) { handleFieldAccessInformation(fieldAccessEOptP.ub) - - tryComputeFinalResult } else { accessState.previousResults.prepend(StringTreeNode.ub) InterimResult.forUB( @@ -195,8 +193,16 @@ class L2FieldReadInterpreter( computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) } else { var trees = accessState.accessDependees.map { ad => - if (ad.hasUBP) ad.ub.sci.tree - else StringTreeNode.ub + if (ad.hasUBP) { + val tree = ad.ub.sci.tree + if (tree.parameterIndices.nonEmpty) { + // We cannot handle write values that contain parameter indices since resolving the parameters + // requires context and this interpreter is present in multiple contexts. + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub + } else + tree + } else StringTreeNode.ub } // 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 @@ -204,8 +210,8 @@ class L2FieldReadInterpreter( if (accessState.fieldAccessDependee.isFinal && !accessState.hasInit) { trees = trees :+ StringTreeNull } - // If an access could not be resolved, append a dynamic element - if (accessState.hasUnresolvableAccess) { + + if (accessState.hasUnresolvableAccess && soundnessMode.isHigh) { trees = trees :+ StringTreeNode.lb } From 11f14e184b173222842d59d6a9bf0c3e489f745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Sep 2024 22:27:11 +0200 Subject: [PATCH 531/583] Move soundness mode into parent package --- .../tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala | 1 - .../analyses/string/{interpretation => }/SoundnessMode.scala | 1 - .../org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala | 1 - .../fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala | 2 +- .../l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala | 2 +- .../l1/interpretation/L1StaticFunctionCallInterpreter.scala | 2 +- .../l1/interpretation/L1VirtualFunctionCallInterpreter.scala | 2 +- .../string/l2/interpretation/L2FieldReadInterpreter.scala | 2 +- .../l2/interpretation/L2VirtualFunctionCallInterpreter.scala | 2 +- 9 files changed, 6 insertions(+), 9 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{interpretation => }/SoundnessMode.scala (95%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala index 5fcdbfd928..25cb19f15d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala @@ -27,7 +27,6 @@ import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala index b8293b3459..2b8122c33a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/SoundnessMode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala @@ -4,7 +4,6 @@ package tac package fpcf package analyses package string -package interpretation trait SoundnessMode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index ae9ec93c1f..ef970e9e0d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -9,7 +9,6 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 4f99898125..755b24bb31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -10,7 +10,7 @@ import scala.collection.mutable import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index b28fe4163f..f4af4fcd67 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -10,7 +10,7 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index f495e9d4c5..c1905fea26 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -12,7 +12,7 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 3839405b2b..ad9bd0b638 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index bfe48fed45..8dc74823a7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -30,8 +30,8 @@ import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.LogContext import org.opalj.log.OPALLogger.logOnce +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.analyses.string.l2.L2StringAnalysis import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index fa23ae8013..09b83a0785 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -21,8 +21,8 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.interpretation.SoundnessMode import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1SystemPropertiesInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1VirtualFunctionCallInterpreter From 9b3059878a6c0ced1568c01ae8f9fe2df0b30b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:13:23 +0200 Subject: [PATCH 532/583] Add soundness mode to more interpreters --- .../opalj/fpcf/fixtures/string_analysis/Loops.java | 4 ++-- .../string_analysis/SimpleControlStructures.java | 2 ++ .../l0/interpretation/BinaryExprInterpreter.scala | 14 +++++++++++--- .../interpretation/L0InterpretationHandler.scala | 2 +- .../interpretation/L1InterpretationHandler.scala | 6 +++--- .../L1NonVirtualMethodCallInterpreter.scala | 9 ++++++--- .../interpretation/L2InterpretationHandler.scala | 7 +++---- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java index 1f0d3ecf4d..415b352958 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -45,7 +45,7 @@ public void simpleForLoopWithKnownBounds() { @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "((x|^-?\\d+$))*yz") @Failure(n = 0, levels = Level.L0) - @PartiallyConstant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(^-?\\d+$|x)yz") + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "xyz") @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); @@ -77,7 +77,7 @@ public void nestedLoops(int range) { @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) @Failure(n = 0, levels = Level.L0) - @PartiallyConstant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(^-?\\d+$|x)yz") + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "xyz") @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void stringBufferExample() { StringBuffer sb = new StringBuffer(); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java index 22052565b7..4ccc5b9069 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java @@ -17,6 +17,7 @@ public void analyzeString(String s) {} @Dynamic(n = 0, levels = Level.TRUTH, value = "(^-?\\d+$|x)") @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "x") @Constant(n = 1, levels = Level.TRUTH, value = "(42-42|x)") @Failure(n = 1, levels = Level.L0) public void ifElseWithStringBuilderWithIntExpr() { @@ -37,6 +38,7 @@ public void ifElseWithStringBuilderWithIntExpr() { @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(3.142.71828|^-?\\d*\\.{0,1}\\d+$2.71828)") @Failure(n = 0, levels = Level.L0) + @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "3.142.71828") public void ifElseWithStringBuilderWithFloatExpr() { StringBuilder sb1 = new StringBuilder(); int i = new Random().nextInt(); diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index 88b6b23d7d..9a63c26721 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -11,13 +11,16 @@ import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch */ -object BinaryExprInterpreter extends AssignmentBasedStringInterpreter { +case class BinaryExprInterpreter()( + implicit val soundnessMode: SoundnessMode +) extends AssignmentBasedStringInterpreter { override type E = BinaryExpr[V] @@ -32,11 +35,16 @@ object BinaryExprInterpreter extends AssignmentBasedStringInterpreter { override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { + // IMPROVE Use the underlying domain to retrieve the result of such expressions if possible in low soundness mode computeFinalResult(expr.cTpe match { - case ComputationalTypeInt => + case ComputationalTypeInt if soundnessMode.isHigh => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicInt) - case ComputationalTypeFloat => + case ComputationalTypeInt => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeNode.ub) + case ComputationalTypeFloat if soundnessMode.isHigh => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicFloat) + case ComputationalTypeFloat => + StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeNode.ub) case _ => StringFlowFunctionProperty.identity }) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index d3455b1383..b425be8456 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -24,7 +24,7 @@ class L0InterpretationHandler(implicit override val project: SomeProject) extend ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) case ExprStmt(_, expr: VirtualFunctionCall[V]) => StringInterpreter.failure(expr.receiver.asVar) case ExprStmt(_, expr: NonVirtualFunctionCall[V]) => StringInterpreter.failure(expr.receiver.asVar) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 275e15a96d..1cf0233d6e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -26,7 +26,7 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) @@ -51,8 +51,8 @@ class L1InterpretationHandler(implicit override val project: SomeProject) extend case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - case vmc: VirtualMethodCall[V] => L1VirtualMethodCallInterpreter.interpret(vmc) - case nvmc: NonVirtualMethodCall[V] => L1NonVirtualMethodCallInterpreter.interpret(nvmc) + case vmc: VirtualMethodCall[V] => L1VirtualMethodCallInterpreter().interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => L1NonVirtualMethodCallInterpreter().interpret(nvmc) case Assignment(_, target, _) => StringInterpreter.failure(target) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 3e22bcc0d2..48198f0fd1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -15,7 +15,9 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** * @author Maximilian Rüsch */ -object L1NonVirtualMethodCallInterpreter extends StringInterpreter { +case class L1NonVirtualMethodCallInterpreter()( + implicit val soundnessMode: SoundnessMode +) extends StringInterpreter { override type T = NonVirtualMethodCall[V] @@ -32,14 +34,15 @@ object L1NonVirtualMethodCallInterpreter extends StringInterpreter { init.params.size match { case 0 => computeFinalResult(StringFlowFunctionProperty.constForVariableAt(pc, targetVar, StringTreeEmptyConst)) - case _ => - // Only StringBuffer and StringBuilder are interpreted which have constructors with <= 1 parameters + case 1 => val paramVar = init.params.head.asVar.toPersistentForm(state.tac.stmts) computeFinalResult( Set(PDUWeb(pc, targetVar), PDUWeb(pc, paramVar)), (env: StringTreeEnvironment) => env.update(pc, targetVar, env(pc, paramVar)) ) + case _ => + failure(targetVar) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala index 09e046292d..cb707eb73b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -70,13 +70,12 @@ class L2InterpretationHandler(implicit override val project: SomeProject) extend case ExprStmt(_, _: StaticFunctionCall[V]) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) - // TODO: For binary expressions, use the underlying domain to retrieve the result of such expressions - case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter.interpretExpr(stmt, expr) + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) case vmc: VirtualMethodCall[V] => - L1VirtualMethodCallInterpreter.interpret(vmc) + L1VirtualMethodCallInterpreter().interpret(vmc) case nvmc: NonVirtualMethodCall[V] => - L1NonVirtualMethodCallInterpreter.interpret(nvmc) + L1NonVirtualMethodCallInterpreter().interpret(nvmc) case Assignment(_, _, _: New) => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) From 964dcbbe460a6bfe4627840bfa8f977857554e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:14:42 +0200 Subject: [PATCH 533/583] Restrict constructor call interpretation to string relevant types --- .../L1NonVirtualMethodCallInterpreter.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 48198f0fd1..44e47e222b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -7,6 +7,7 @@ package string package l1 package interpretation +import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -23,8 +24,12 @@ case class L1NonVirtualMethodCallInterpreter()( override def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { instr.name match { - case "" => interpretInit(instr) - case _ => computeFinalResult(StringFlowFunctionProperty.identity) + case "" + if instr.declaringClass.asReferenceType == ObjectType.StringBuffer || + instr.declaringClass.asReferenceType == ObjectType.StringBuilder || + instr.declaringClass.asReferenceType == ObjectType.String => + interpretInit(instr) + case _ => computeFinalResult(StringFlowFunctionProperty.identity) } } From 10733bf81226230b7c8cba6f602ff9d1d847388b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:15:50 +0200 Subject: [PATCH 534/583] Remove write threshold from field read interpretation --- OPAL/tac/src/main/resources/reference.conf | 3 +- .../analyses/string/l2/L2StringAnalysis.scala | 5 --- .../L2FieldReadInterpreter.scala | 41 ------------------- 3 files changed, 1 insertion(+), 48 deletions(-) diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index c8a50efce0..64b19a84aa 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1505,8 +1505,7 @@ org.opalj { StringAnalysis { highSoundness = false, maxDepth = 30 - }, - l1.L1StringAnalysis.fieldWriteThreshold = 100 + } } } }, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala index 377d2aec61..d1f7e89d07 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala @@ -19,11 +19,6 @@ import org.opalj.tac.fpcf.analyses.string.l2.interpretation.L2InterpretationHand /** * @author Maximilian Rüsch */ -object L2StringAnalysis { - - private[l2] final val ConfigLogCategory = "analysis configuration - l1 string analysis" -} - object LazyL2StringAnalysis { def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index 8dc74823a7..4b8d6e0bfe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -26,13 +26,8 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP -import org.opalj.log.Error -import org.opalj.log.Info -import org.opalj.log.LogContext -import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string.l2.L2StringAnalysis import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** @@ -51,31 +46,6 @@ class L2FieldReadInterpreter( override type E = FieldRead[V] - private final val FieldWriteThresholdConfigKey = { - "org.opalj.fpcf.analyses.string.l1.L1StringAnalysis.fieldWriteThreshold" - } - - /** - * 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. - */ - private val fieldWriteThreshold = { - implicit val logContext: LogContext = project.logContext - val threshold = - try { - project.config.getInt(FieldWriteThresholdConfigKey) - } catch { - case t: Throwable => - logOnce { - Error(L2StringAnalysis.ConfigLogCategory, s"couldn't read: $FieldWriteThresholdConfigKey", t) - } - 10 - } - - logOnce(Info(L2StringAnalysis.ConfigLogCategory, "uses a field write threshold of " + threshold)) - threshold - } - private case class FieldReadState( target: PV, var fieldAccessDependee: EOptionP[DeclaredField, FieldWriteAccessInformation], @@ -105,13 +75,6 @@ class L2FieldReadInterpreter( } } - /** - * Currently, fields are approximated using the following approach: If a field of a type not supported by the - * [[L2StringAnalysis]] is passed, a flow function producing the LB will be produced. 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 - * [[org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC]]. - */ override def interpretExpr(target: PV, fieldRead: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { @@ -141,10 +104,6 @@ class L2FieldReadInterpreter( accessState: FieldReadState, state: InterpretationState ): ProperPropertyComputationResult = { - if (accessInformation.accesses.length > fieldWriteThreshold) { - return computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) - } - if (accessState.fieldAccessDependee.isFinal && accessInformation.accesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value return computeFinalResult(computeUBWithNewTree(StringTreeOr.fromNodes( From d714256926c828e4876e3d625663d70f58b51bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:18:24 +0200 Subject: [PATCH 535/583] Improve maximum depth detection --- .../properties/string/StringTreeNode.scala | 43 +++++++++++++++++++ .../fpcf/analyses/string/StringAnalysis.scala | 34 +++++++-------- .../analyses/string/StringAnalysisState.scala | 4 +- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index f87b2dcee6..502eb33f81 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -16,6 +16,13 @@ sealed trait StringTreeNode { val children: Seq[StringTreeNode] lazy val depth: Int = children.map(_.depth).maxOption.getOrElse(0) + 1 + final def limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (depth <= targetDepth) + this + else + _limitToDepth(targetDepth, replacement) + } + protected def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode def sorted: StringTreeNode @@ -106,6 +113,13 @@ case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNod def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = StringTreeRepetition(child.replaceParameters(parameters)) + + def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth == 1) + replacement + else + StringTreeRepetition(child.limitToDepth(targetDepth - 1, replacement)) + } } case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { @@ -154,6 +168,13 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends this } } + + def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth == 1) + replacement + else + StringTreeConcat(children.map(_.limitToDepth(targetDepth - 1, replacement))) + } } object StringTreeConcat { @@ -239,6 +260,13 @@ private case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNo this } } + + def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth == 1) + replacement + else + SeqBasedStringTreeOr(children.map(_.limitToDepth(targetDepth - 1, replacement))) + } } object SeqBasedStringTreeOr { @@ -256,6 +284,8 @@ object SeqBasedStringTreeOr { case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) extends StringTreeOr { + override lazy val depth: Int = _children.map(_.depth).maxOption.getOrElse(0) + 1 + override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.map(_.sorted).sortBy(_.toRegex)) override def _simplify: StringTreeNode = SetBasedStringTreeOr._simplifySelf { @@ -274,6 +304,13 @@ case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) ext this } } + + def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth == 1) + replacement + else + SetBasedStringTreeOr(_children.map(_.limitToDepth(targetDepth - 1, replacement))) + } } object SetBasedStringTreeOr { @@ -319,6 +356,12 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { override final def simplify: StringTreeNode = this override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this + override def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth == 1) + replacement + else + limitToDepth(targetDepth - 1, replacement) + } } case class StringTreeConst(string: String) extends SimpleStringTreeNode { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index a9a5a819ee..5faebcd8de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -18,6 +18,7 @@ import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.NoCallers import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.EPS @@ -77,33 +78,32 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec } private def computeResults(implicit state: ContextFreeStringAnalysisState): ProperPropertyComputationResult = { - if (state.hasDependees) { - InterimResult( - state.entity, - StringConstancyProperty.lb, - computeNewUpperBound(state), - state.dependees, - continuation(state) - ) - } else { - Result(state.entity, computeNewUpperBound(state)) - } - } - - private def computeNewUpperBound(state: ContextFreeStringAnalysisState): StringConstancyProperty = { - StringConstancyProperty(state.stringFlowDependee match { + val newProperty = StringConstancyProperty(state.stringFlowDependee match { case UBP(methodStringFlow) => val tree = methodStringFlow(state.entity.pc, state.entity.pv) - if (tree.depth > maxDepth) { + if (tree.depth == maxDepth) { // String constancy information got too complex, abort. This guard can probably be removed once // recursing functions are properly handled using e.g. the widen-converge approach. - StringConstancyInformation.lb + state.hitMaximumDepth = true + StringConstancyInformation(tree.limitToDepth(maxDepth, StringTreeNode.lb)) } else { StringConstancyInformation(tree.simplify) } case _: EPK[_, MethodStringFlow] => StringConstancyInformation.ub }) + + if (state.hasDependees && !state.hitMaximumDepth) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + newProperty, + state.dependees, + continuation(state) + ) + } else { + Result(state.entity, newProperty) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 80c1f689b8..efc66eb331 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -26,7 +26,8 @@ import org.opalj.tac.fpcf.properties.string.MethodStringFlow private[string] case class ContextFreeStringAnalysisState( entity: VariableDefinition, - var stringFlowDependee: EOptionP[Method, MethodStringFlow] + var stringFlowDependee: EOptionP[Method, MethodStringFlow], + var hitMaximumDepth: Boolean = false ) { def hasDependees: Boolean = stringFlowDependee.isRefinable @@ -45,7 +46,6 @@ private[string] class ContextStringAnalysisState private ( def updateStringDependee(stringDependee: EPS[VariableDefinition, StringConstancyProperty]): Unit = { _stringDependee = stringDependee - // TODO parameter indices should always grow <- when the new string dependee only contains additional information _parameterIndices ++= stringDependee.ub.sci.tree.parameterIndices } private def stringTree: StringTreeNode = _stringDependee.ub.sci.tree From 823c006779f4a8970e6027cb44da400e0fb7e2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:19:28 +0200 Subject: [PATCH 536/583] Support more java serialized class strings --- .../fpcf/fixtures/string_analysis/Integration.java | 13 +++++++++++++ .../properties/string_analysis/StringMatcher.scala | 10 +++++++++- .../br/fpcf/properties/string/StringTreeNode.scala | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java index 27b697e4f9..b4004df140 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java @@ -41,4 +41,17 @@ public void knownHierarchyInstanceTest() { public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); } + + @Constant(n = 0, levels = Level.TRUTH, value = "\\[B") + @Constant(n = 1, levels = Level.TRUTH, value = "\\[Ljava.lang.String;") + @Constant(n = 2, levels = Level.TRUTH, value = "\\[\\[Lsun.security.pkcs.SignerInfo;") + @Constant(n = 3, levels = Level.TRUTH, value = "US\\$") + @Constant(n = 4, levels = Level.TRUTH, value = "US\\\\") + public void regexCompilableTest() { + analyzeString("[B"); + analyzeString("[Ljava.lang.String;"); + analyzeString("[[Lsun.security.pkcs.SignerInfo;"); + analyzeString("US$"); + analyzeString("US\\"); + } } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala index 9b328e4903..ca1a2fc4f7 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala @@ -4,6 +4,9 @@ package fpcf package properties package string_analysis +import java.util.regex.Pattern +import scala.util.Try + import org.opalj.br.AnnotationLike import org.opalj.br.ObjectType import org.opalj.br.analyses.Project @@ -46,7 +49,12 @@ sealed abstract class ConstancyStringMatcher(val constancyLevel: StringConstancy if (expectedConstancy != actLevel || expectedStrings != actString) { Some(s"Level: $expectedConstancy, Strings: $expectedStrings") } else { - None + val patternTry = Try(Pattern.compile(actString)) + if (patternTry.isFailure) { + Some(s"Same string, but it does not compile to a regex: ${patternTry.failed.get}") + } else { + None + } } } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 502eb33f81..d2b7ad09e3 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -365,7 +365,7 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { } case class StringTreeConst(string: String) extends SimpleStringTreeNode { - override def _toRegex: String = Regex.quoteReplacement(string) + override def _toRegex: String = Regex.quoteReplacement(string).replaceAll("\\[", "\\\\[") override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT From bbcc3c43019868960da3b812d0099500dd3df480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:20:59 +0200 Subject: [PATCH 537/583] Improve return value soundness mode for several interpreters --- .../fixtures/string_analysis/External.java | 4 ++- .../L1VirtualFunctionCallInterpreter.scala | 11 +++++-- .../L1VirtualMethodCallInterpreter.scala | 15 +++++---- .../L2FieldReadInterpreter.scala | 8 ++--- .../L2VirtualFunctionCallInterpreter.scala | 33 +++++++++---------- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index 928ff58c4f..eabd2a753a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -87,7 +87,8 @@ public void fieldInitByConstructorParameter() { // Contains a field write in the same method which cannot be captured by flow functions @Constant(n = 0, levels = Level.TRUTH, value = "(some value|^null$)") @Failure(n = 0, levels = { Level.L0, Level.L1 }) - @Dynamic(n = 0, levels = Level.L2, value = ".*") + @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "some value") + @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = ".*") public void fieldWriteInSameMethod() { writeInSameMethodField = "some value"; analyzeString(writeInSameMethodField); @@ -95,6 +96,7 @@ public void fieldWriteInSameMethod() { @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*|^null$)") @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "^null$") public void fieldWithNoWriteTest() { analyzeString(noWriteField); } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index ad9bd0b638..bdfc2a94ff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -59,13 +59,15 @@ class L1VirtualFunctionCallInterpreter( computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, at.get, - StringTreeDynamicInt + if (soundnessMode.isHigh) StringTreeDynamicInt + else StringTreeNode.ub )) case FloatType | DoubleType if at.isDefined => computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, at.get, - StringTreeDynamicFloat + if (soundnessMode.isHigh) StringTreeDynamicFloat + else StringTreeNode.ub )) case _ if at.isDefined => interpretArbitraryCall(at.get, call) @@ -111,6 +113,8 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends Assignme */ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { + val soundnessMode: SoundnessMode + override type E = VirtualFunctionCall[V] def interpretAppendCall(at: Option[PV], pt: PV, call: E)(implicit @@ -138,7 +142,8 @@ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringI if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { valueState } else { - StringTreeDynamicFloat + if (soundnessMode.isHigh) StringTreeDynamicFloat + else StringTreeNode.ub } case _ => valueState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 0d71ba197c..639acbbd10 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -18,14 +18,12 @@ import org.opalj.value.TheIntegerValue /** * @author Maximilian Rüsch */ -object L1VirtualMethodCallInterpreter extends StringInterpreter { +case class L1VirtualMethodCallInterpreter()( + implicit val soundnessMode: SoundnessMode +) extends StringInterpreter { override type T = VirtualMethodCall[V] - /** - * Currently, this function supports no method calls. However, it treats [[StringBuilder.setLength]] such that it - * will return the lower bound for now. - */ override def interpret(call: T)(implicit state: InterpretationState): ProperPropertyComputationResult = { val pReceiver = call.receiver.asVar.toPersistentForm(state.tac.stmts) @@ -49,7 +47,12 @@ object L1VirtualMethodCallInterpreter extends StringInterpreter { sb.setLength(intVal) env.update(state.pc, pReceiver, StringTreeConst(sb.toString())) case _ => - env.update(state.pc, pReceiver, StringTreeNode.lb) + env.update( + state.pc, + pReceiver, + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub + ) } } ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala index 4b8d6e0bfe..5081ca4918 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala @@ -15,7 +15,6 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeNull import org.opalj.br.fpcf.properties.string.StringTreeOr @@ -107,7 +106,8 @@ class L2FieldReadInterpreter( if (accessState.fieldAccessDependee.isFinal && accessInformation.accesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value return computeFinalResult(computeUBWithNewTree(StringTreeOr.fromNodes( - StringTreeDynamicString, + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub, StringTreeNull ))) } @@ -146,10 +146,10 @@ class L2FieldReadInterpreter( accessState: FieldReadState, state: InterpretationState ): ProperPropertyComputationResult = { - if (accessState.hasWriteInSameMethod) { + if (accessState.hasWriteInSameMethod && soundnessMode.isHigh) { // We cannot handle writes to a field that is read in the same method at the moment as the flow functions do // not capture field state. This can be improved upon in the future. - computeFinalResult(computeUBWithNewTree(StringTreeDynamicString)) + computeFinalResult(computeUBWithNewTree(StringTreeNode.lb)) } else { var trees = accessState.accessDependees.map { ad => if (ad.hasUBP) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index 09b83a0785..3c3822ae97 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -8,7 +8,6 @@ package l2 package interpretation import org.opalj.br.DefinedMethod -import org.opalj.br.Method import org.opalj.br.ObjectType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider @@ -70,7 +69,9 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi override val target: PV, override val parameters: Seq[PV], methodContext: Context, - var calleeDependee: EOptionP[DefinedMethod, Callees] + var calleeDependee: EOptionP[DefinedMethod, Callees], + var seenDirectCallees: Int = 0, + var seenIndirectCallees: Int = 0 ) extends FunctionCallState(target, parameters) { override def hasDependees: Boolean = calleeDependee.isRefinable || super.hasDependees @@ -82,6 +83,7 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi state: InterpretationState ): ProperPropertyComputationResult = { val params = getParametersForPC(state.pc).map(_.asVar.toPersistentForm(state.tac.stmts)) + // IMPROVE pass the actual method context through the entity - needs to be differentiated from "upward" entities val depender = CalleeDepender(target, params, contextProvider.newContext(state.dm), ps(state.dm, Callees.key)) if (depender.calleeDependee.isEPK) { @@ -102,10 +104,18 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi )(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case UBP(c: Callees) => + val newCallees = c.directCallees(callState.methodContext, state.pc).drop(callState.seenDirectCallees) ++ + c.indirectCallees(callState.methodContext, state.pc).drop(callState.seenIndirectCallees) + + // IMPROVE add some uncertainty element if methods with unknown body exist + val newMethods = newCallees + .filter(_.method.hasSingleDefinedMethod) + .map(_.method.definedMethod) + .filterNot(callState.calleeMethods.contains) + .distinct.toList.sortBy(_.classFile.fqn) + callState.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] - val newMethods = getNewMethodsFromCallees(callState.methodContext, c)(state, callState) - if (newMethods.isEmpty && eps.isFinal) { - // Improve add previous results back + if (newMethods.isEmpty && callState.calleeMethods.isEmpty && eps.isFinal) { failure(callState.target)(state, soundnessMode) } else { for { @@ -120,17 +130,4 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi case _ => super.continuation(state, callState)(eps) } } - - private def getNewMethodsFromCallees(context: Context, callees: Callees)(implicit - state: InterpretationState, - callState: CallState - ): Seq[Method] = { - // IMPROVE only process newest callees - callees.callees(context, state.pc) - // IMPROVE add some uncertainty element if methods with unknown body exist - .filter(_.method.hasSingleDefinedMethod) - .map(_.method.definedMethod) - .filterNot(callState.calleeMethods.contains) - .distinct.toList.sortBy(_.classFile.fqn) - } } From deb608c9dc0892fd0797e177b4d4b63584b0b7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 21:42:49 +0200 Subject: [PATCH 538/583] Introduce l3 and raise field access interpretation --- .../fixtures/string_analysis/ArrayOps.java | 6 +- .../fixtures/string_analysis/Complex.java | 2 +- .../fixtures/string_analysis/External.java | 26 ++--- .../string_analysis/FunctionCalls.java | 2 +- .../fpcf/fixtures/string_analysis/Loops.java | 52 ++++----- .../SimpleControlStructures.java | 4 +- .../SimpleStringBuilderOps.java | 2 +- .../string_analysis/SimpleStringOps.java | 6 +- .../properties/string_analysis/Level.java | 3 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 41 ++++++- .../L2InterpretationHandler.scala | 12 +- .../analyses/string/l3/L3StringAnalysis.scala | 43 ++++++++ .../L3FieldReadInterpreter.scala} | 4 +- .../L3InterpretationHandler.scala | 103 ++++++++++++++++++ 14 files changed, 240 insertions(+), 66 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{l2/interpretation/L2FieldReadInterpreter.scala => l3/interpretation/L3FieldReadInterpreter.scala} (99%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java index 90b862aa10..7430daa17e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java @@ -17,7 +17,7 @@ public class ArrayOps { public void analyzeString(String s) {} @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.String|java.lang.StringBuilder|java.lang.System|java.lang.Runnable)") - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "array accesses cannot be interpreted yet") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }, reason = "arrays are not supported") public void fromStringArray(int index) { String[] classes = { "java.lang.String", "java.lang.StringBuilder", @@ -29,7 +29,7 @@ public void fromStringArray(int index) { } @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)") - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "arrays are not supported") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }, reason = "arrays are not supported") public void arrayStaticAndVirtualFunctionCalls(int i) { String[] classes = { "java.lang.Object", @@ -41,7 +41,7 @@ public void arrayStaticAndVirtualFunctionCalls(int i) { } @Constant(n = 0, levels = Level.TRUTH, value = "(January|February|March|April)") - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }, reason = "arrays are not supported") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }, reason = "arrays are not supported") public void getStringArrayField(int i) { analyzeString(monthNames[i]); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java index a3407bd135..3690d5d40e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java @@ -63,7 +63,7 @@ public void complexDependencyResolve(String s, Class clazz) { @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") @Failure(n = 0, levels = Level.L0) // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - @Constant(n = 0, levels = { Level.L1, Level.L2 }, value = "((Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest|Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, value = "((Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest|Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)") public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { String shaderName = getHelloWorld() + "_" + "paintname"; if (getPaintType) { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java index eabd2a753a..63e6ff1e7b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java @@ -35,13 +35,13 @@ public External(float e) { public void analyzeString(String s) {} @Constant(n = 0, levels = Level.TRUTH, value = "private l0 non-final string field") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void nonFinalFieldRead() { analyzeString(nonFinalNonStaticField); } @Constant(n = 0, levels = Level.TRUTH, value = "will not be revealed here") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void nonFinalStaticFieldRead() { analyzeString(nonFinalStaticField); } @@ -56,19 +56,19 @@ public void publicFinalStaticFieldRead() { } @Constant(n = 0, levels = Level.TRUTH, value = "init field value") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void fieldWithInitRead() { analyzeString(fieldWithSelfInit.toString()); } @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*Impl_Stub") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void fieldWithInitWithOutOfScopeRead() { analyzeString(fieldWithSelfInitWithOutOfScopeCall); } @Constant(n = 0, levels = Level.TRUTH, value = "initialized by constructor") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void fieldInitByConstructorRead() { analyzeString(fieldWithConstructorInit.toString()); } @@ -79,29 +79,29 @@ public void fieldInitByConstructorRead() { @Invalid(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.LOW) @Dynamic(n = 0, levels = Level.L1, domains = DomainLevel.L2, soundness = SoundnessMode.HIGH, value = "^-?\\d*\\.{0,1}\\d+$", reason = "the field value is inlined using L2 domains") - @Invalid(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW) + @Invalid(n = 0, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.LOW) public void fieldInitByConstructorParameter() { analyzeString(new StringBuilder().append(fieldWithConstructorParameterInit).toString()); } // Contains a field write in the same method which cannot be captured by flow functions @Constant(n = 0, levels = Level.TRUTH, value = "(some value|^null$)") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) - @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "some value") - @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = ".*") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) + @Constant(n = 0, levels = Level.L3, soundness = SoundnessMode.LOW, value = "some value") + @Dynamic(n = 0, levels = Level.L3, soundness = SoundnessMode.HIGH, value = ".*") public void fieldWriteInSameMethod() { writeInSameMethodField = "some value"; analyzeString(writeInSameMethodField); } @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*|^null$)") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) - @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "^null$") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) + @Constant(n = 0, levels = Level.L3, soundness = SoundnessMode.LOW, value = "^null$") public void fieldWithNoWriteTest() { analyzeString(noWriteField); } - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }) public void nonSupportedFieldTypeRead() { analyzeString(unsupportedTypeField.toString()); } @@ -130,7 +130,7 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { * Methods are called that return a string but are not within this project => cannot / will not interpret */ @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }) @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") public void methodsOutOfScopeTest() throws FileNotFoundException { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java index dab1197f1c..38b2e568bf 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java @@ -114,7 +114,7 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { */ @Constant(n = 0, levels = Level.TRUTH, value = "val") @Failure(n = 0, levels = { Level.L0, Level.L1 }, domains = DomainLevel.L1) - @Constant(n = 0, levels = Level.L2, domains = DomainLevel.L1, value = "(One|java.lang.Object|val)") + @Constant(n = 0, levels = { Level.L2, Level.L3 }, domains = DomainLevel.L1, value = "(One|java.lang.Object|val)") public void resolvableReturnValue() { analyzeString(resolvableReturnValueFunction("val", 42)); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java index 415b352958..d4174bbc35 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java @@ -22,12 +22,12 @@ public void analyzeString(String s) {} */ @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "a(b)*") @Failure(n = 1, levels = Level.L0) - @Constant(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") - @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); for (int i = 0; i < 10; i++) { @@ -45,8 +45,8 @@ public void simpleForLoopWithKnownBounds() { @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "((x|^-?\\d+$))*yz") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "xyz") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "xyz") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 20; i++) { @@ -63,8 +63,8 @@ public void ifElseInLoopWithAppendAfterwards() { @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "a(b)*") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") public void nestedLoops(int range) { for (int i = 0; i < range; i++) { StringBuilder sb = new StringBuilder("a"); @@ -77,8 +77,8 @@ public void nestedLoops(int range) { @PartiallyConstant(n = 0, value = "((x|^-?\\d+$))*yz", levels = Level.TRUTH) @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "xyz") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "xyz") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "(.*|.*yz)") public void stringBufferExample() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 20; i++) { @@ -95,8 +95,8 @@ public void stringBufferExample() { @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") public void whileTrueWithBreak() { StringBuilder sb = new StringBuilder("a"); while (true) { @@ -110,8 +110,8 @@ public void whileTrueWithBreak() { @PartiallyConstant(n = 0, value = "a(b)*", levels = Level.TRUTH) @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "ab") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "ab") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") public void whileNonTrueWithBreak(int i) { StringBuilder sb = new StringBuilder("a"); int j = 0; @@ -127,17 +127,17 @@ public void whileNonTrueWithBreak(int i) { @Constant(n = 0, levels = Level.TRUTH, value = "(iv1|iv2): ") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(iv1|iv2): ") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "(iv1|iv2): ") // The real value is not fully resolved yet, since the string builder is used in a while loop, // which leads to the string builder potentially carrying any value. This can be refined by // recording pc specific states during data flow analysis. - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "((iv1|iv2): |.*)") @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(iv1|iv2): ((great!)?)*(java.lang.Runtime)?") @Failure(n = 1, levels = Level.L0) @Constant(n = 1, levels = Level.L1, soundness = SoundnessMode.LOW, value = "(iv1|iv2): great!") @Dynamic(n = 1, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|.*.*)") - @Constant(n = 1, levels = Level.L2, soundness = SoundnessMode.LOW, value = "((iv1|iv2): great!|(iv1|iv2): great!java.lang.Runtime)") - @Dynamic(n = 1, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") + @Constant(n = 1, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "((iv1|iv2): great!|(iv1|iv2): great!java.lang.Runtime)") + @Dynamic(n = 1, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "(.*|.*java.lang.Runtime)") public void extensiveWithManyControlStructures(boolean cond) { StringBuilder sb = new StringBuilder(); if (cond) { @@ -167,17 +167,17 @@ public void extensiveWithManyControlStructures(boolean cond) { // The bytecode produces an "if" within an "if" inside the first loop => two conditions @Constant(n = 0, levels = Level.TRUTH, value = "abc((d)?)*") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "(abc|abcd)") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "(abc|abcd)") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") @Constant(n = 1, levels = Level.TRUTH, value = "") @Failure(n = 1, levels = Level.L0) - @Constant(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "") - @Dynamic(n = 1, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = "(|.*)") + @Constant(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "") + @Dynamic(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "(|.*)") @Dynamic(n = 2, levels = Level.TRUTH, value = "((.*)?)*") @Failure(n = 2, levels = Level.L0) @Constant(n = 2, levels = Level.L1, soundness = SoundnessMode.LOW, value = "") - @Constant(n = 2, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(|java.lang.Runtime)") - @Dynamic(n = 2, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Constant(n = 2, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "(|java.lang.Runtime)") + @Dynamic(n = 2, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") public void breakContinueExamples(int value) { StringBuilder sb1 = new StringBuilder("abc"); for (int i = 0; i < value; i++) { @@ -220,9 +220,9 @@ public void breakContinueExamples(int value) { @Constant(n = 0, levels = Level.TRUTH, value = "Hello: (java.lang.Runtime|java.lang.StringBuilder|StringBuilder)?") @Failure(n = 0, levels = Level.L0) @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "Hello: ") - @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, + @Constant(n = 0, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "(Hello: |Hello: StringBuilder|Hello: java.lang.Runtime|Hello: java.lang.StringBuilder)") - @Dynamic(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.HIGH, value = ".*") + @Dynamic(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = ".*") protected void setDebugFlags(String[] var1) { for(int var2 = 0; var2 < var1.length; ++var2) { String var3 = var1[var2]; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java index 4ccc5b9069..b7f7221523 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java @@ -17,7 +17,7 @@ public void analyzeString(String s) {} @Dynamic(n = 0, levels = Level.TRUTH, value = "(^-?\\d+$|x)") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "x") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "x") @Constant(n = 1, levels = Level.TRUTH, value = "(42-42|x)") @Failure(n = 1, levels = Level.L0) public void ifElseWithStringBuilderWithIntExpr() { @@ -38,7 +38,7 @@ public void ifElseWithStringBuilderWithIntExpr() { @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(3.142.71828|^-?\\d*\\.{0,1}\\d+$2.71828)") @Failure(n = 0, levels = Level.L0) - @Constant(n = 0, levels = { Level.L1, Level.L2 }, soundness = SoundnessMode.LOW, value = "3.142.71828") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "3.142.71828") public void ifElseWithStringBuilderWithFloatExpr() { StringBuilder sb1 = new StringBuilder(); int i = new Random().nextInt(); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java index 777a1b52ce..e25b0d3fc9 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java @@ -30,7 +30,7 @@ public void multipleDirectAppends() { @Failure(n = 0, levels = Level.L0) @Constant(n = 1, levels = Level.TRUTH, value = "SomeOther") @Failure(n = 1, levels = Level.L0) - @Constant(n = 1, levels = { Level.L1, Level.L2 }, value = "(Some|SomeOther)") + @Constant(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, value = "(Some|SomeOther)") public void stringValueOfWithStringBuilder() { StringBuilder sb = new StringBuilder("Some"); sb.append("Other"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java index d7977f11ac..ac11095515 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java @@ -264,13 +264,13 @@ public void switchNestedWithNestedDefault(int value, int value2) { * A more comprehensive case where multiple definition sites have to be considered each with a different string * generation mechanism */ - @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.System|java.lang.StringBuilder)") + @Constant(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.System|java.lang.StringBuilder)") @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "java.lang.System") @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.System)") @Constant(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW, value = "java.lang.System") @Dynamic(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(.*|java.lang..*|java.lang.System)") - @Constant(n = 0, levels = Level.L2, soundness = SoundnessMode.LOW, value = "(java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") - @Dynamic(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") + @Constant(n = 0, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, value = "(java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") + @Dynamic(n = 0, levels = { Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, value = "(.*|java.lang.StringBuilder|java.lang.StringBuilder|java.lang.System)") public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java index 2d05ba6526..23449821a3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java @@ -9,7 +9,8 @@ public enum Level { TRUTH("TRUTH"), L0("L0"), L1("L1"), - L2("L2"); + L2("L2"), + L3("L3"); private final String value; 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 bb3209ee75..4664cba2e7 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 @@ -56,6 +56,7 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis +import org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringAnalysis import org.opalj.tac.fpcf.analyses.systemproperties.EagerSystemPropertiesAnalysisScheduler sealed abstract class StringAnalysisTest extends PropertiesTest { @@ -335,6 +336,40 @@ sealed abstract class L2StringAnalysisTest extends StringAnalysisTest { override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL2StringAnalysis.allRequiredAnalyses :+ + EagerSystemPropertiesAnalysisScheduler + } +} + +class L2StringAnalysisWithL1DefaultDomainTest extends L2StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L1 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW +} + +class L2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.LOW +} + +class HighSoundnessL2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { + + override def domainLevel: DomainLevel = DomainLevel.L2 + override def soundnessMode: SoundnessMode = SoundnessMode.HIGH +} + +/** + * Tests whether the [[org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringAnalysis]] works correctly with + * respect to some well-defined tests. + * + * @author Maximilian Rüsch + */ +sealed abstract class L3StringAnalysisTest extends StringAnalysisTest { + + override def level = Level.L3 + + override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { + LazyL3StringAnalysis.allRequiredAnalyses :+ EagerFieldAccessInformationAnalysis :+ EagerSystemPropertiesAnalysisScheduler } @@ -347,19 +382,19 @@ sealed abstract class L2StringAnalysisTest extends StringAnalysisTest { } } -class L2StringAnalysisWithL1DefaultDomainTest extends L2StringAnalysisTest { +class L3StringAnalysisWithL1DefaultDomainTest extends L3StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L1 override def soundnessMode: SoundnessMode = SoundnessMode.LOW } -class L2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { +class L3StringAnalysisWithL2DefaultDomainTest extends L3StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.LOW } -class HighSoundnessL2StringAnalysisWithL2DefaultDomainTest extends L2StringAnalysisTest { +class HighSoundnessL3StringAnalysisWithL2DefaultDomainTest extends L3StringAnalysisTest { override def domainLevel: DomainLevel = DomainLevel.L2 override def soundnessMode: SoundnessMode = SoundnessMode.HIGH diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala index cb707eb73b..b6548eb63d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -7,8 +7,6 @@ package string package l2 package interpretation -import org.opalj.br.analyses.DeclaredFields -import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey @@ -30,7 +28,6 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty */ class L2InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { - implicit val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) override protected def processStatement(implicit @@ -41,12 +38,7 @@ class L2InterpretationHandler(implicit override val project: SomeProject) extend // Currently unsupported case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) - - case stmt @ Assignment(_, _, expr: FieldRead[V]) => - new L2FieldReadInterpreter().interpretExpr(stmt, expr) - // Field reads without result usage are irrelevant - case ExprStmt(_, _: FieldRead[V]) => - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + case Assignment(_, target, _: FieldRead[V]) => StringInterpreter.failure(target) case stmt: FieldWriteAccessStmt[V] => StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( @@ -96,7 +88,7 @@ class L2InterpretationHandler(implicit override val project: SomeProject) extend object L2InterpretationHandler { - def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, ContextProviderKey) + def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) def apply(project: SomeProject): L2InterpretationHandler = new L2InterpretationHandler()(project) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala new file mode 100644 index 0000000000..f22394874f --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l3 + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.properties.SystemProperties +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.l3.interpretation.L3InterpretationHandler + +/** + * @author Maximilian Rüsch + */ +object LazyL3StringAnalysis { + + def allRequiredAnalyses: Seq[FPCFLazyAnalysisScheduler] = Seq( + LazyStringAnalysis, + LazyMethodStringFlowAnalysis, + LazyL3StringFlowAnalysis + ) +} + +object LazyL3StringFlowAnalysis extends LazyStringFlowAnalysis { + + override final def uses: Set[PropertyBounds] = super.uses ++ PropertyBounds.ubs( + Callees, + FieldWriteAccessInformation, + SystemProperties + ) + + override final def init(p: SomeProject, ps: PropertyStore): InitializationData = L3InterpretationHandler(p) + + override def requiredProjectInformation: ProjectInformationKeys = super.requiredProjectInformation ++ + L3InterpretationHandler.requiredProjectInformation +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 5081ca4918..06c418c746 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -4,7 +4,7 @@ package tac package fpcf package analyses package string -package l2 +package l3 package interpretation import scala.collection.mutable.ListBuffer @@ -35,7 +35,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * * @author Maximilian Rüsch */ -class L2FieldReadInterpreter( +class L3FieldReadInterpreter( implicit val ps: PropertyStore, implicit val project: SomeProject, implicit val declaredFields: DeclaredFields, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala new file mode 100644 index 0000000000..0b8f8561cb --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala @@ -0,0 +1,103 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package l3 +package interpretation + +import org.opalj.br.analyses.DeclaredFields +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1StaticFunctionCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1VirtualMethodCallInterpreter +import org.opalj.tac.fpcf.analyses.string.l2.interpretation.L2VirtualFunctionCallInterpreter +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * @inheritdoc + * + * @author Maximilian Rüsch + */ +class L3InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { + + implicit val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) + implicit val contextProvider: ContextProvider = p.get(ContextProviderKey) + + override protected def processStatement(implicit + state: InterpretationState + ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + case stmt @ Assignment(_, _, expr: SimpleValueConst) => + SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) + + // Currently unsupported + case Assignment(_, target, _: ArrayExpr[V]) => StringInterpreter.failure(target) + + case stmt @ Assignment(_, _, expr: FieldRead[V]) => + new L3FieldReadInterpreter().interpretExpr(stmt, expr) + // Field reads without result usage are irrelevant + case ExprStmt(_, _: FieldRead[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case stmt: FieldWriteAccessStmt[V] => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + stmt.pc, + stmt.value.asVar.toPersistentForm(state.tac.stmts) + )) + + case stmt @ Assignment(_, _, expr: VirtualFunctionCall[V]) => + new L2VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => + new L2VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => + L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + + case stmt @ Assignment(_, _, expr: StaticFunctionCall[V]) => + L1StaticFunctionCallInterpreter().interpretExpr(stmt, expr) + // Static function calls without return value usage are irrelevant + case ExprStmt(_, _: StaticFunctionCall[V]) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) + + case vmc: VirtualMethodCall[V] => + L1VirtualMethodCallInterpreter().interpret(vmc) + case nvmc: NonVirtualMethodCall[V] => + L1NonVirtualMethodCallInterpreter().interpret(nvmc) + + case Assignment(_, _, _: New) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + + case Assignment(_, target, _) => + StringInterpreter.failure(target) + + case ReturnValue(pc, expr) => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identityForVariableAt( + pc, + expr.asVar.toPersistentForm(state.tac.stmts) + )) + + case _ => + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.identity) + } +} + +object L3InterpretationHandler { + + def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredFieldsKey, ContextProviderKey) + + def apply(project: SomeProject): L3InterpretationHandler = new L3InterpretationHandler()(project) +} From 34f1f3b8b795f2b5d51f5ed30fcedb1b0699b902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 22:04:31 +0200 Subject: [PATCH 539/583] Fix variable definitions after merge --- .../org/opalj/fpcf/StringAnalysisTest.scala | 2 +- .../src/main/scala/org/opalj/tac/DUVar.scala | 1 + .../src/main/scala/org/opalj/tac/PDUVar.scala | 108 ------------------ .../src/main/scala/org/opalj/tac/PDUWeb.scala | 2 + .../L3FieldReadInterpreter.scala | 1 + .../string/StringTreeEnvironment.scala | 2 + .../main/scala/org/opalj/tac/package.scala | 27 +++-- 7 files changed, 22 insertions(+), 121 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala 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 4664cba2e7..817d0ce6fb 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 @@ -144,7 +144,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } /** - * Extracts [[org.opalj.tac.PUVar]]s from a set of statements. The locations of the [[org.opalj.tac.PUVar]]s are + * Extracts [[org.opalj.br.PUVar]]s from a set of statements. The locations of the [[org.opalj.br.PUVar]]s are * identified by the argument to the very first call to [[nameTestMethod]]. * * @return Returns the arguments of the [[nameTestMethod]] as a PUVars list in the order in which they occurred. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala index 0b4d0d9487..abf4b5de8e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala @@ -10,6 +10,7 @@ import org.opalj.ai.pcOfMethodExternalException import org.opalj.br.ComputationalType import org.opalj.br.ComputationalTypeReturnAddress import org.opalj.br.PDUVar +import org.opalj.br.PDVar import org.opalj.br.PUVar import org.opalj.collection.immutable.IntTrieSet import org.opalj.value.ValueInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala deleted file mode 100644 index cf8362a052..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUVar.scala +++ /dev/null @@ -1,108 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac - -import org.opalj.ai.PCs -import org.opalj.collection.immutable.IntTrieSet -import org.opalj.value.ValueInformation - -/** - * A persistent (i.e. TAC independent) representation of a [[DUVar]]. - */ -abstract class PDUVar[+Value <: ValueInformation] { - - /** - * The information about the variable that were derived by the underlying data-flow analysis. - */ - def value: Value - - /** - * The PCs of the instructions which use this variable. - * - * '''Defined, if and only if this is an assignment statement.''' - */ - def usePCs: PCs - - /** - * The PCs of the instructions which initialize this variable/ - * the origin of the value identifies the expression which initialized this variable. - * - * '''Defined, if and only if this is a variable usage.''' - * - * For more information see [[DUVar.definedBy]] - */ - def defPCs: PCs - - def toValueOriginForm(implicit pcToIndex: Array[Int]): DUVar[Value] -} - -class PDVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( - val value: Value, - val usePCs: PCs -) extends PDUVar[Value] { - - def defPCs: Nothing = throw new UnsupportedOperationException - - override def equals(other: Any): Boolean = { - other match { - case that: PDVar[_] => this.usePCs == that.usePCs - case _ => false - } - } - - override def toString: String = { - s"PDVar(usePCs=${usePCs.mkString("{", ",", "}")},value=$value)" - } - - def toValueOriginForm(implicit pcToIndex: Array[Int]): Nothing = throw new UnsupportedOperationException -} - -object PDVar { - - def apply(d: org.opalj.ai.ValuesDomain)( - value: d.DomainValue, - usePCs: PCs - ): PDVar[d.DomainValue] = { - new PDVar[d.DomainValue](value, usePCs) - } - - def apply[Value <: ValueInformation](value: Value, useSites: IntTrieSet): PDVar[Value] = new PDVar(value, useSites) - - def unapply[Value <: ValueInformation](d: PDVar[Value]): Some[(Value, IntTrieSet)] = Some((d.value, d.usePCs)) -} - -class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( - val value: Value, - val defPCs: PCs -) extends PDUVar[Value] { - - def usePCs: Nothing = throw new UnsupportedOperationException - - override def equals(other: Any): Boolean = { - other match { - case that: PUVar[_] => this.defPCs == that.defPCs - case _ => false - } - } - - override def toString: String = { - s"PUVar(defPCs=${defPCs.mkString("{", ",", "}")},value=$value)" - } - - override def toValueOriginForm( - implicit pcToIndex: Array[Int] - ): UVar[Value] = UVar(value, valueOriginsOfPCs(defPCs, pcToIndex)) -} - -object PUVar { - - def apply(d: org.opalj.ai.ValuesDomain)( - value: d.DomainValue, - defPCs: PCs - ): PUVar[d.DomainValue] = { - new PUVar[d.DomainValue](value, defPCs) - } - - def apply[Value <: ValueInformation](value: Value, defPCs: IntTrieSet): PUVar[Value] = new PUVar(value, defPCs) - - def unapply[Value <: ValueInformation](u: PUVar[Value]): Some[(Value, IntTrieSet)] = Some((u.value, u.defPCs)) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala index e09b08e9fe..8620c8db23 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala @@ -2,6 +2,8 @@ package org.opalj package tac +import org.opalj.br.PDVar +import org.opalj.br.PUVar import org.opalj.collection.immutable.IntTrieSet import org.opalj.value.ValueInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 06c418c746..71c8208a1c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -10,6 +10,7 @@ package interpretation import scala.collection.mutable.ListBuffer import org.opalj.br.DeclaredField +import org.opalj.br.PUVar import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.analyses.ContextProvider diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 2760969989..d0cfd8fdc4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -5,6 +5,8 @@ package fpcf package properties package string +import org.opalj.br.PDVar +import org.opalj.br.PUVar import org.opalj.br.fpcf.properties.string.SetBasedStringTreeOr import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala index 31539ac773..3934286574 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/package.scala @@ -16,6 +16,7 @@ import org.opalj.ai.pcOfMethodExternalException import org.opalj.br.ExceptionHandler import org.opalj.br.ExceptionHandlers import org.opalj.br.PCs +import org.opalj.br.PDUVar import org.opalj.br.PUVar import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG @@ -55,19 +56,21 @@ package object tac { ) } + final def valueOriginOfPC(pc: Int, pcToIndex: Array[Int]): Option[ValueOrigin] = { + if (ai.underlyingPC(pc) < 0) + Some(pc) // parameter + else if (pc >= 0 && pcToIndex(pc) >= 0) + Some(pcToIndex(pc)) // local + else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) + Some(ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc)))) + else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) + Some(ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc)))) + else + None + } + final def valueOriginsOfPCs(pcs: PCs, pcToIndex: Array[Int]): IntTrieSet = { - pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => - if (ai.underlyingPC(pc) < 0) - origins + pc // parameter - else if (pc >= 0 && pcToIndex(pc) >= 0) - origins + pcToIndex(pc) // local - else if (isImmediateVMException(pc) && pcToIndex(pcOfImmediateVMException(pc)) >= 0) - origins + ValueOriginForImmediateVMException(pcToIndex(pcOfImmediateVMException(pc))) - else if (isMethodExternalExceptionOrigin(pc) && pcToIndex(pcOfMethodExternalException(pc)) >= 0) - origins + ValueOriginForMethodExternalException(pcToIndex(pcOfMethodExternalException(pc))) - else - origins // as is - } + pcs.foldLeft(EmptyIntTrieSet: IntTrieSet) { (origins, pc) => origins ++ valueOriginOfPC(pc, pcToIndex) } } /** From e502d1b26e881d88301e72a6336af11de6197f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 23:30:22 +0200 Subject: [PATCH 540/583] Improve performance of string tree environments --- .../string/StringTreeEnvironment.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index d0cfd8fdc4..f1e12b27e3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -9,23 +9,20 @@ import org.opalj.br.PDVar import org.opalj.br.PUVar import org.opalj.br.fpcf.properties.string.SetBasedStringTreeOr import org.opalj.br.fpcf.properties.string.StringTreeNode -import org.opalj.br.fpcf.properties.string.StringTreeOr /** * @author Maximilian Rüsch */ case class StringTreeEnvironment private ( private val map: Map[PDUWeb, StringTreeNode], - private val pcToWebs: Map[Int, Seq[PDUWeb]] + private val pcToWebs: Map[Int, Set[PDUWeb]] ) { - private def getWebsFor(pc: Int, pv: PV): Seq[PDUWeb] = { - val webs = pv match { + private def getWebsFor(pc: Int, pv: PV): Set[PDUWeb] = { + pv match { case _: PDVar[_] => pcToWebs(pc) - case _: PUVar[_] => pv.defPCs.map(pcToWebs).toSeq.flatten + case _: PUVar[_] => pv.defPCs.map(pcToWebs).flatten } - - webs.sortBy(_.defPCs.toList.toSet.min) } private def getWebsFor(web: PDUWeb): Seq[PDUWeb] = map.keys @@ -51,7 +48,8 @@ case class StringTreeEnvironment private ( } } - def mergeAllMatching(pc: Int, pv: PV): StringTreeNode = StringTreeOr.fromNodes(getWebsFor(pc, pv).map(map(_)): _*) + def mergeAllMatching(pc: Int, pv: PV): StringTreeNode = + SetBasedStringTreeOr.createWithSimplify(getWebsFor(pc, pv).map(map(_))) def apply(pc: Int, pv: PV): StringTreeNode = map(getWebFor(pc, pv)) @@ -76,8 +74,8 @@ object StringTreeEnvironment { def apply(map: Map[PDUWeb, StringTreeNode]): StringTreeEnvironment = { val pcToWebs = - map.keys.toSeq.flatMap(web => web.defPCs.map((_, web))).groupMap(_._1)(_._2) - .withDefaultValue(Seq.empty) + map.keySet.flatMap(web => web.defPCs.map((_, web))).groupMap(_._1)(_._2) + .withDefaultValue(Set.empty) StringTreeEnvironment(map, pcToWebs) } From 1c5a3eb60120a0fedd359f3b0753081cb0336e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 23:30:42 +0200 Subject: [PATCH 541/583] Fix indents in config --- OPAL/tac/src/main/resources/reference.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 98f6e1ca61..97e8c2c2ff 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1533,14 +1533,14 @@ org.opalj { string { InterpretationHandler.highSoundness = false, MethodStringFlowAnalysis { - excludedPackages = [ - "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets - ], - highSoundness = false + excludedPackages = [ + "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets + ], + highSoundness = false }, StringAnalysis { - highSoundness = false, - maxDepth = 30 + highSoundness = false, + maxDepth = 30 } } } From a4e762a19569f8ef3178d5813042a8359789dcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 23:31:18 +0200 Subject: [PATCH 542/583] Limit string tree depth to 10 --- OPAL/tac/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 97e8c2c2ff..653a3862f3 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1540,7 +1540,7 @@ org.opalj { }, StringAnalysis { highSoundness = false, - maxDepth = 30 + maxDepth = 10 } } } From dee9b5f307852ae2edff0dccc54a6e953caa7402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Sun, 8 Sep 2024 23:43:04 +0200 Subject: [PATCH 543/583] Add some improve comments --- .../fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java | 4 ++++ .../analyses/string/flowanalysis/StructuralAnalysis.scala | 1 + .../l0/interpretation/SimpleValueConstExprInterpreter.scala | 2 ++ .../string/l2/interpretation/L2InterpretationHandler.scala | 1 + .../analyses/systemproperties/SystemPropertiesAnalysis.scala | 1 + 5 files changed, 9 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java index e25b0d3fc9..d912d37b05 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java @@ -179,4 +179,8 @@ public void withUnknownAppendSource(String filename) throws IOException { sb.append(data); analyzeString(sb.toString()); } + + // IMPROVE Add the following tests + // - Passing string builders as call parameters should result in a failure (called method can modify string builder) + // - Support generic function calls, in particular with upper return type bounds that are non-object } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index 86cea1d73c..e7b65b7992 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -348,6 +348,7 @@ object StructuralAnalysis { } if (reachUnder.exists(m => graph.get(startingNode).pathTo(graph.get(m)).isEmpty)) { + // IMPROVE reliably detect size of improper regions and reduce throw new IllegalStateException("This implementation of structural analysis cannot handle improper regions!") } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala index e7080ca284..b65381c2d4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala @@ -21,6 +21,8 @@ object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { + // IMPROVE the constants generated here could also be interpreted outside string flow interpretation, reducing + // the overhead needed to interpret a simple string const for e.g. a call parameter. computeFinalResult(expr match { case ic: IntConst => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(ic.value.toString)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala index b6548eb63d..2004ae13a9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -51,6 +51,7 @@ class L2InterpretationHandler(implicit override val project: SomeProject) extend case stmt @ ExprStmt(_, expr: VirtualFunctionCall[V]) => new L2VirtualFunctionCallInterpreter().interpretExpr(stmt, expr) + // IMPROVE add call-graph based interpreters for other call types than virtual function calls to L2 case stmt @ Assignment(_, _, expr: NonVirtualFunctionCall[V]) => L1NonVirtualFunctionCallInterpreter().interpretExpr(stmt, expr) case stmt @ ExprStmt(_, expr: NonVirtualFunctionCall[V]) => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala index 24df97e801..49f3944452 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala @@ -46,6 +46,7 @@ class SystemPropertiesAnalysis private[analyses] ( private type Values = Set[StringTreeNode] def processMethod(callContext: ContextType, tacaiEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { + // IMPROVE add initialization framework similar to the EntryPointFinder framework implicit val state: State = new SystemPropertiesState(callContext, tacaiEP, Map.empty) var values: Values = Set.empty From d7d4ee997064491096fd7a52204ed27c3412b29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 22:43:54 +0200 Subject: [PATCH 544/583] Add trivial string analysis --- OPAL/tac/src/main/resources/reference.conf | 3 +- .../trivial/TrivialStringAnalysis.scala | 157 ++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 03b8e2025b..b4a005f3fd 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1541,7 +1541,8 @@ org.opalj { StringAnalysis { highSoundness = false, maxDepth = 10 - } + }, + TrivialStringAnalysis.highSoundness = false } } }, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala new file mode 100644 index 0000000000..5ac6bb4aa7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -0,0 +1,157 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package trivial + +import org.opalj.br.Method +import org.opalj.br.PDVar +import org.opalj.br.PUVar +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.properties.string.StringConstancyInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPS +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.OPALLogger.logOnce +import org.opalj.tac.fpcf.properties.TACAI + +/** + * Provides only the most trivial information about available strings for a given variable. + * + * If the variable represents a constant string, the string value will be captured and returned in the result. + * If the variable represents any other value, no string value can be derived and the analysis returns either the upper + * or lower bound depending on the soundness mode. + * + * @author Maximilian Rüsch + * + * @see [[StringAnalysis]] + */ +class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { + + private final val ConfigLogCategory = "analysis configuration - trivial string analysis" + + private val soundnessMode: SoundnessMode = { + val mode = + try { + SoundnessMode(project.config.getBoolean(TrivialStringAnalysis.SoundnessModeConfigKey)) + } catch { + case t: Throwable => + logOnce { + Error(ConfigLogCategory, s"couldn't read: ${TrivialStringAnalysis.SoundnessModeConfigKey}", t) + } + SoundnessMode(false) + } + + logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) + mode + } + + private case class TrivialStringAnalysisState(entity: VariableContext, var tacDependee: EOptionP[Method, TACAI]) + + def analyze(variableContext: VariableContext): ProperPropertyComputationResult = { + implicit val state: TrivialStringAnalysisState = TrivialStringAnalysisState( + variableContext, + ps(variableContext.m, TACAI.key) + ) + + if (state.tacDependee.isRefinable) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + Set(state.tacDependee), + continuation(state) + ) + } else if (state.tacDependee.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + Result(state.entity, StringConstancyProperty(StringConstancyInformation(failure))) + } else { + determinePossibleStrings + } + } + + private def continuation(state: TrivialStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case tacaiEPS: EPS[_, _] if eps.pk == TACAI.key => + state.tacDependee = tacaiEPS.asInstanceOf[EPS[Method, TACAI]] + determinePossibleStrings(state) + + case _ => + throw new IllegalArgumentException(s"Unknown EPS given in continuation: $eps") + } + } + + private def determinePossibleStrings( + implicit state: TrivialStringAnalysisState + ): ProperPropertyComputationResult = { + val tac = state.tacDependee.ub.tac.get + + def mapDefPCToStringTree(defPC: Int): StringTreeNode = { + if (defPC < 0) { + failure + } else { + tac.stmts(valueOriginOfPC(defPC, tac.pcToIndex).get).asAssignment.expr match { + case StringConst(_, v) => StringTreeConst(v) + case _ => failure + } + } + } + + val tree = state.entity.pv match { + case PUVar(_, defPCs) => + StringTreeOr(defPCs.map(pc => mapDefPCToStringTree(pc))) + + case PDVar(_, _) => + mapDefPCToStringTree(state.entity.pc) + } + + Result(state.entity, StringConstancyProperty(StringConstancyInformation(tree))) + } + + private def failure: StringTreeNode = { + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub + } +} + +private object TrivialStringAnalysis { + + private final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.TrivialStringAnalysis.highSoundness" +} + +/** + * @author Maximilian Rüsch + * + * @see [[TrivialStringAnalysis]] + */ +object LazyTrivialStringAnalysis extends BasicFPCFLazyAnalysisScheduler { + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) + + override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.lub(StringConstancyProperty)) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) + + override def register(project: SomeProject, propertyStore: PropertyStore, i: InitializationData): FPCFAnalysis = { + val analysis = new TrivialStringAnalysis(project) + propertyStore.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) + analysis + } +} From a2c09bf2a194060a206ace2f9f019051e0372017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 22:48:17 +0200 Subject: [PATCH 545/583] Move the method string flow analysis and its state into the flow analysis package --- .../org/opalj/fpcf/StringAnalysisTest.scala | 2 +- .../analyses/string/InterpretationState.scala | 22 +++++++++++++++++++ .../string/StringAnalysisScheduler.scala | 1 + .../MethodStringFlowAnalysis.scala | 17 +++++++------- .../MethodStringFlowAnalysisState.scala} | 20 +++-------------- 5 files changed, 35 insertions(+), 27 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{ => flowanalysis}/MethodStringFlowAnalysis.scala (87%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{ComputationState.scala => flowanalysis/MethodStringFlowAnalysisState.scala} (87%) 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 817d0ce6fb..9916f1db2f 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 @@ -49,9 +49,9 @@ import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis -import org.opalj.tac.fpcf.analyses.string.MethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.StringAnalysis import org.opalj.tac.fpcf.analyses.string.VariableContext +import org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala new file mode 100644 index 0000000000..54f9e2a4d2 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala @@ -0,0 +1,22 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org +package opalj +package tac +package fpcf +package analyses +package string + +import org.opalj.br.DefinedMethod +import org.opalj.br.Method +import org.opalj.fpcf.EOptionP +import org.opalj.tac.fpcf.properties.TACAI + +case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { + + def tac: TAC = { + if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) + tacDependee.ub.tac.get + else + throw new IllegalStateException("Cannot get a TAC from a TACAI with no or empty upper bound!") + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala index 7a0351f049..e638f4590c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala @@ -16,6 +16,7 @@ import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala similarity index 87% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index 25cb19f15d..64b193bd50 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -4,6 +4,7 @@ package tac package fpcf package analyses package string +package flowanalysis import scala.jdk.CollectionConverters._ @@ -23,10 +24,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.log.Error import org.opalj.log.Info import org.opalj.log.OPALLogger.logOnce -import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis -import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph -import org.opalj.tac.fpcf.analyses.string.flowanalysis.Statement -import org.opalj.tac.fpcf.analyses.string.flowanalysis.StructuralAnalysis import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -77,7 +74,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(method: Method): ProperPropertyComputationResult = { - val state = ComputationState(method, declaredMethods(method), ps(method, TACAI.key)) + val state = MethodStringFlowAnalysisState(method, declaredMethods(method), ps(method, TACAI.key)) if (excludedPackages.exists(method.classFile.thisType.packageName.startsWith(_))) { Result(state.entity, MethodStringFlow.lb) @@ -101,7 +98,9 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn * 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(implicit state: ComputationState): ProperPropertyComputationResult = { + private def determinePossibleStrings(implicit + state: MethodStringFlowAnalysisState + ): ProperPropertyComputationResult = { implicit val tac: TAC = state.tac state.flowGraph = FlowGraph(tac.cfg) @@ -121,7 +120,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn computeResults } - private def continuation(state: ComputationState)(eps: SomeEPS): ProperPropertyComputationResult = { + private def continuation(state: MethodStringFlowAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { case FinalP(_: TACAI) if eps.pk.equals(TACAI.key) => state.tacDependee = eps.asInstanceOf[FinalEP[Method, TACAI]] @@ -136,7 +135,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } } - private def computeResults(implicit state: ComputationState): ProperPropertyComputationResult = { + private def computeResults(implicit state: MethodStringFlowAnalysisState): ProperPropertyComputationResult = { if (state.hasDependees) { InterimResult.forUB( state.entity, @@ -149,7 +148,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } } - private def computeNewUpperBound(state: ComputationState): MethodStringFlow = { + private def computeNewUpperBound(state: MethodStringFlowAnalysisState): MethodStringFlow = { val startEnv = state.getStartEnvAndReset MethodStringFlow(state.flowAnalysis.compute(state.getFlowFunctionsByPC)(startEnv)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala similarity index 87% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala index 4ef05c4711..f0b15b541a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/ComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org -package opalj +package org.opalj package tac package fpcf package analyses package string +package flowanalysis import scala.collection.mutable @@ -16,10 +16,6 @@ import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.EOptionP -import org.opalj.tac.fpcf.analyses.string.flowanalysis.ControlTree -import org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis -import org.opalj.tac.fpcf.analyses.string.flowanalysis.FlowGraph -import org.opalj.tac.fpcf.analyses.string.flowanalysis.SuperFlowGraph import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -30,7 +26,7 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment * 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(entity: Method, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { +case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { def tac: TAC = { if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) @@ -125,13 +121,3 @@ case class ComputationState(entity: Method, dm: DefinedMethod, var tacDependee: _startEnv } } - -case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { - - def tac: TAC = { - if (tacDependee.hasUBP && tacDependee.ub.tac.isDefined) - tacDependee.ub.tac.get - else - throw new IllegalStateException("Cannot get a TAC from a TACAI with no or empty upper bound!") - } -} From 1d41aaa9bf46389670deff4a17c771605f7cbbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 22:49:16 +0200 Subject: [PATCH 546/583] Move the interpretation state into the interpretation package --- .../opalj/tac/fpcf/analyses/string/StringInterpreter.scala | 1 + .../string/{ => interpretation}/InterpretationState.scala | 4 ++-- .../string/l0/interpretation/BinaryExprInterpreter.scala | 1 + .../string/l0/interpretation/L0InterpretationHandler.scala | 1 + .../l0/interpretation/SimpleValueConstExprInterpreter.scala | 1 + .../string/l1/interpretation/L1FunctionCallInterpreter.scala | 1 + .../string/l1/interpretation/L1InterpretationHandler.scala | 1 + .../interpretation/L1NonVirtualFunctionCallInterpreter.scala | 1 + .../l1/interpretation/L1NonVirtualMethodCallInterpreter.scala | 1 + .../l1/interpretation/L1StaticFunctionCallInterpreter.scala | 1 + .../l1/interpretation/L1SystemPropertiesInterpreter.scala | 1 + .../l1/interpretation/L1VirtualFunctionCallInterpreter.scala | 1 + .../l1/interpretation/L1VirtualMethodCallInterpreter.scala | 1 + .../string/l2/interpretation/L2InterpretationHandler.scala | 1 + .../l2/interpretation/L2VirtualFunctionCallInterpreter.scala | 1 + .../string/l3/interpretation/L3FieldReadInterpreter.scala | 1 + .../string/l3/interpretation/L3InterpretationHandler.scala | 1 + 17 files changed, 18 insertions(+), 2 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{ => interpretation}/InterpretationState.scala (93%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index ef970e9e0d..5066af4b26 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -9,6 +9,7 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala index 54f9e2a4d2..b2fdfd4662 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/InterpretationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org -package opalj +package org.opalj package tac package fpcf package analyses package string +package interpretation import org.opalj.br.DefinedMethod import org.opalj.br.Method diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index 9a63c26721..99c545586e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index b425be8456..0ebf8bd903 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala index b65381c2d4..03d708fe53 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala @@ -9,6 +9,7 @@ package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 387de25aa8..74db8151a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -19,6 +19,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 1cf0233d6e..9c08de5d7d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index f4af4fcd67..deaaf762a2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -11,6 +11,7 @@ import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.TACAI /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 44e47e222b..aefb48d582 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -10,6 +10,7 @@ package interpretation import org.opalj.br.ObjectType import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index c1905fea26..99a071e2b3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.string.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala index 13a7b9fa93..9e83d66001 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala @@ -18,6 +18,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty private[string] trait L1SystemPropertiesInterpreter extends StringInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index bdfc2a94ff..5657aa2472 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -22,6 +22,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.SoundnessMode +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 639acbbd10..f12261bd3c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -11,6 +11,7 @@ import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala index 2004ae13a9..10400cf63d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualFunctionCallInterpreter diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index 3c3822ae97..64cb34a605 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -22,6 +22,7 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1FunctionCallInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1SystemPropertiesInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1VirtualFunctionCallInterpreter diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 71c8208a1c..818404f02d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -28,6 +28,7 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala index 0b8f8561cb..fecaa358d7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala @@ -15,6 +15,7 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.analyses.string.l0.interpretation.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string.l0.interpretation.SimpleValueConstExprInterpreter import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1NonVirtualFunctionCallInterpreter From 7c3b47c904a2e9d028ac63deb72e2730c5fb2175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 22:51:17 +0200 Subject: [PATCH 547/583] Rename test fixture package to match fpcf package name --- .../fpcf/fixtures/{string_analysis => string}/ArrayOps.java | 4 ++-- .../fpcf/fixtures/{string_analysis => string}/Complex.java | 2 +- .../ExceptionalControlStructures.java | 2 +- .../fpcf/fixtures/{string_analysis => string}/External.java | 2 +- .../fixtures/{string_analysis => string}/FunctionCalls.java | 4 ++-- .../fixtures/{string_analysis => string}/Integration.java | 6 +++--- .../fpcf/fixtures/{string_analysis => string}/Loops.java | 2 +- .../SimpleControlStructures.java | 2 +- .../{string_analysis => string}/SimpleStringBuilderOps.java | 2 +- .../{string_analysis => string}/SimpleStringOps.java | 2 +- .../{string_analysis => string}/tools/GreetingService.java | 2 +- .../{string_analysis => string}/tools/HelloGreeting.java | 2 +- .../tools/SimpleHelloGreeting.java | 2 +- .../{string_analysis => string}/tools/StringProvider.java | 2 +- .../src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 2 +- 15 files changed, 19 insertions(+), 19 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/ArrayOps.java (94%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/Complex.java (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/ExceptionalControlStructures.java (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/External.java (99%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/FunctionCalls.java (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/Integration.java (91%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/Loops.java (99%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/SimpleControlStructures.java (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/SimpleStringBuilderOps.java (99%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/SimpleStringOps.java (99%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/tools/GreetingService.java (71%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/tools/HelloGreeting.java (79%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/tools/SimpleHelloGreeting.java (79%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_analysis => string}/tools/StringProvider.java (91%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java similarity index 94% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java index 7430daa17e..0eb50bdac7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ArrayOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.fixtures.string_analysis.tools.StringProvider; +import org.opalj.fpcf.fixtures.string.tools.StringProvider; import org.opalj.fpcf.properties.string_analysis.*; /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java index 3690d5d40e..0a12b8e1e1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java index 027aa15020..9771b8d4cc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java index 63e6ff1e7b..e67e87e856 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java index 38b2e568bf..31899924d1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.fixtures.string_analysis.tools.StringProvider; +import org.opalj.fpcf.fixtures.string.tools.StringProvider; import org.opalj.fpcf.properties.string_analysis.*; /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java similarity index 91% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java index b4004df140..fc9157a217 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Integration.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java @@ -1,8 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.fixtures.string_analysis.tools.GreetingService; -import org.opalj.fpcf.fixtures.string_analysis.tools.HelloGreeting; +import org.opalj.fpcf.fixtures.string.tools.GreetingService; +import org.opalj.fpcf.fixtures.string.tools.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.*; /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java index d4174bbc35..a1001a3d84 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java index b7f7221523..d6b6ee9fe6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java index d912d37b05..48abb61329 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java index ac11095515..9a7544c5cb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis; +package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.properties.string_analysis.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java similarity index 71% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java index 4ed815d9b2..0b29615e86 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/GreetingService.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.tools; +package org.opalj.fpcf.fixtures.string.tools; public interface GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java similarity index 79% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java index ff1aafee41..26d0e0bbbe 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/HelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.tools; +package org.opalj.fpcf.fixtures.string.tools; public class HelloGreeting implements GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java similarity index 79% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java index a5e9fe3304..85bf0cf3e0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/SimpleHelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.tools; +package org.opalj.fpcf.fixtures.string.tools; public class SimpleHelloGreeting implements GreetingService { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringProvider.java similarity index 91% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringProvider.java index 160633b43a..a326374a31 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/tools/StringProvider.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringProvider.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_analysis.tools; +package org.opalj.fpcf.fixtures.string.tools; public class StringProvider { 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 9916f1db2f..d5301f81ec 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 @@ -85,7 +85,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { .withValue(StringAnalysis.MaxDepthConfigKey, ConfigValueFactory.fromAnyRef(30)) } - override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string_analysis") + override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string") override final def init(p: Project[URL]): Unit = { val domain = domainLevel match { From ea80b6604fbed8884a3ee8425651d395a0c7297f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 23:01:14 +0200 Subject: [PATCH 548/583] Remove intermediate string constancy information --- .../info/StringAnalysisReflectiveCalls.scala | 10 ++--- .../string_analysis/StringMatcher.scala | 2 +- .../string/StringConstancyInformation.scala | 21 ---------- .../string/StringConstancyProperty.scala | 24 +++++------ .../fpcf/analyses/string/StringAnalysis.scala | 15 ++++--- .../analyses/string/StringAnalysisState.scala | 42 ++++++++----------- .../L1FunctionCallInterpreter.scala | 2 +- .../L3FieldReadInterpreter.scala | 2 +- .../trivial/TrivialStringAnalysis.scala | 5 +-- .../SystemPropertiesAnalysis.scala | 4 +- 10 files changed, 46 insertions(+), 81 deletions(-) delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.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 index e153dc60bf..0ea6cf63ac 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 @@ -22,9 +22,9 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysesManagerKey import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.analyses.ContextProvider -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyLevel import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.instructions.Instruction import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.instructions.INVOKEVIRTUAL @@ -281,7 +281,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { implicit val (propertyStore, _) = manager.runAll(configuration.analyses) // Stores the obtained results for each supported reflective operation - val resultMap = mutable.Map[String, ListBuffer[StringConstancyInformation]]() + val resultMap = mutable.Map[String, ListBuffer[StringTreeNode]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } project.allMethodsWithBody.foreach { m => @@ -314,9 +314,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { case (e, callName) => propertyStore.properties(e).toIndexedSeq.foreach { case FinalP(p: StringConstancyProperty) => - resultMap(callName).append(p.stringConstancyInformation) + resultMap(callName).append(p.tree) case InterimEUBP(_, ub: StringConstancyProperty) => - resultMap(callName).append(ub.stringConstancyInformation) + resultMap(callName).append(ub.tree) case _ => println(s"No result for $e in $callName found!") } @@ -326,7 +326,7 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { resultMapToReport(resultMap) } - private def resultMapToReport(resultMap: mutable.Map[String, ListBuffer[StringConstancyInformation]]): BasicReport = { + private def resultMapToReport(resultMap: mutable.Map[String, ListBuffer[StringTreeNode]]): BasicReport = { val report = ListBuffer[String]("Results of the Reflection Analysis:") for ((reflectiveCall, entries) <- resultMap) { var constantCount, partConstantCount, dynamicCount = 0 diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala index ca1a2fc4f7..48399a7126 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala @@ -20,7 +20,7 @@ sealed trait StringMatcher extends AbstractPropertyMatcher { protected def getActualValues: Property => Option[(String, String)] = { case prop: StringConstancyProperty => - val tree = prop.sci.tree.simplify.sorted + val tree = prop.tree.simplify.sorted if (tree.isInvalid) { None } else { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala deleted file mode 100644 index e169a11ab9..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyInformation.scala +++ /dev/null @@ -1,21 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package fpcf -package properties -package string - -/** - * @author Maximilian Rüsch - */ -case class StringConstancyInformation(tree: StringTreeNode) { - - final def constancyLevel: StringConstancyLevel.Value = tree.constancyLevel - final def toRegex: String = tree.toRegex -} - -object StringConstancyInformation { - - def lb: StringConstancyInformation = StringConstancyInformation(StringTreeNode.lb) - def ub: StringConstancyInformation = StringConstancyInformation(StringTreeNode.ub) -} diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index c1a632d316..35860b1d3a 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -17,28 +17,25 @@ sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformat } class StringConstancyProperty( - val stringConstancyInformation: StringConstancyInformation + val tree: StringTreeNode ) extends Property with StringConstancyPropertyMetaInformation { - def sci: StringConstancyInformation = stringConstancyInformation - final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - val level = stringConstancyInformation.constancyLevel.toString.toLowerCase - val strings = if (stringConstancyInformation.tree.simplify.isInvalid) { + val level = tree.constancyLevel.toString.toLowerCase + val strings = if (tree.simplify.isInvalid) { "No possible strings - Invalid Flow" - } else stringConstancyInformation.tree.sorted.toRegex + } else tree.sorted.toRegex s"Level: $level, Possible Strings: $strings" } - override def hashCode(): Int = stringConstancyInformation.hashCode() + override def hashCode(): Int = tree.hashCode() override def equals(o: Any): Boolean = o match { - case scp: StringConstancyProperty => - stringConstancyInformation.equals(scp.stringConstancyInformation) - case _ => false + case scp: StringConstancyProperty => tree.equals(scp.tree) + case _ => false } } @@ -53,16 +50,15 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta ) } - def apply(stringConstancyInformation: StringConstancyInformation): StringConstancyProperty = - new StringConstancyProperty(stringConstancyInformation) + def apply(tree: StringTreeNode): StringConstancyProperty = new StringConstancyProperty(tree) /** * @return Returns the lower bound from a lattice-point of view. */ - def lb: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation.lb) + def lb: StringConstancyProperty = StringConstancyProperty(StringTreeNode.lb) /** * @return Returns the upper bound from a lattice-point of view. */ - def ub: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation.ub) + def ub: StringConstancyProperty = StringConstancyProperty(StringTreeNode.ub) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 5faebcd8de..69abba5210 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -16,7 +16,6 @@ import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.NoCallers -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.EOptionP @@ -85,12 +84,12 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec // String constancy information got too complex, abort. This guard can probably be removed once // recursing functions are properly handled using e.g. the widen-converge approach. state.hitMaximumDepth = true - StringConstancyInformation(tree.limitToDepth(maxDepth, StringTreeNode.lb)) + tree.limitToDepth(maxDepth, StringTreeNode.lb) } else { - StringConstancyInformation(tree.simplify) + tree.simplify } case _: EPK[_, MethodStringFlow] => - StringConstancyInformation.ub + StringTreeNode.ub }) if (state.hasDependees && !state.hitMaximumDepth) { @@ -160,12 +159,12 @@ class ContextStringAnalysis(override val project: SomeProject) extends StringAna InterimResult( state.entity, StringConstancyProperty.lb, - StringConstancyProperty(state.currentSciUB), + StringConstancyProperty(state.currentTreeUB), state.dependees, continuation(state) ) } else { - Result(state.entity, StringConstancyProperty(state.currentSciUB)) + Result(state.entity, StringConstancyProperty(state.currentTreeUB)) } } } @@ -277,12 +276,12 @@ private[string] class MethodParameterContextStringAnalysis(override val project: InterimResult( state.entity, StringConstancyProperty.lb, - StringConstancyProperty(state.currentSciUB), + StringConstancyProperty(state.currentTreeUB), state.dependees, continuation(state) ) } else { - Result(state.entity, StringConstancyProperty(state.finalSci)) + Result(state.entity, StringConstancyProperty(state.finalTree)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index efc66eb331..4b2232f8d1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -12,7 +12,6 @@ import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.fpcf.properties.string.StringTreeOr @@ -46,9 +45,9 @@ private[string] class ContextStringAnalysisState private ( def updateStringDependee(stringDependee: EPS[VariableDefinition, StringConstancyProperty]): Unit = { _stringDependee = stringDependee - _parameterIndices ++= stringDependee.ub.sci.tree.parameterIndices + _parameterIndices ++= stringDependee.ub.tree.parameterIndices } - private def stringTree: StringTreeNode = _stringDependee.ub.sci.tree + private def stringTree: StringTreeNode = _stringDependee.ub.tree def parameterIndices: Set[Int] = _parameterIndices // Parameter StringConstancy @@ -68,19 +67,19 @@ private[string] class ContextStringAnalysisState private ( def updateParamDependee(dependee: EOptionP[MethodParameterContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def currentSciUB: StringConstancyInformation = { + def currentTreeUB: StringTreeNode = { if (_stringDependee.hasUBP) { val paramTrees = _paramDependees.map { kv => ( kv._1.index, - if (kv._2.hasUBP) kv._2.ub.sci.tree + if (kv._2.hasUBP) kv._2.ub.tree else StringTreeNode.ub ) }.toMap - StringConstancyInformation(stringTree.replaceParameters(paramTrees).simplify) + stringTree.replaceParameters(paramTrees).simplify } else { - StringConstancyInformation.ub + StringTreeNode.ub } } @@ -101,7 +100,7 @@ object ContextStringAnalysisState { entity: VariableContext, stringDependee: EOptionP[VariableDefinition, StringConstancyProperty] ): ContextStringAnalysisState = { - val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.sci.tree.parameterIndices + val parameterIndices = if (stringDependee.hasUBP) stringDependee.ub.tree.parameterIndices else Set.empty[Int] new ContextStringAnalysisState(entity, stringDependee, parameterIndices) } @@ -157,7 +156,6 @@ private[string] case class MethodParameterContextStringAnalysisState( private val _methodToEntityMapping: mutable.Map[DefinedMethod, Seq[VariableContext]] = mutable.Map.empty private val _paramDependees: mutable.Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = mutable.Map.empty - private var _methodsWithInvalidParamReferences: Set[Int] = Set.empty[Int] def registerParameterDependee( dm: DefinedMethod, @@ -176,35 +174,29 @@ private[string] case class MethodParameterContextStringAnalysisState( } def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def registerInvalidParamReference(dmId: Int): Unit = - _methodsWithInvalidParamReferences += dmId - def currentSciUB: StringConstancyInformation = { + def currentTreeUB: StringTreeNode = { if (_discoveredUnknownTAC) { - StringConstancyInformation(StringTreeNode.lb) + StringTreeNode.lb } else { val paramOptions = _methodToEntityMapping.keys.toSeq .sortBy(_.id) .flatMap { dm => - if (_methodsWithInvalidParamReferences.contains(dm.id)) { - Some(StringTreeNode.ub) - } else { - _methodToEntityMapping(dm) - .map(_paramDependees) - .filter(_.hasUBP) - .map(_.ub.sci.tree) - } + _methodToEntityMapping(dm) + .map(_paramDependees) + .filter(_.hasUBP) + .map(_.ub.tree) } - StringConstancyInformation(StringTreeOr(paramOptions).simplify) + StringTreeOr(paramOptions).simplify } } - def finalSci: StringConstancyInformation = { + def finalTree: StringTreeNode = { if (_methodToEntityMapping.isEmpty) { - StringConstancyInformation(StringTreeNode.lb) + StringTreeNode.lb } else { - currentSciUB + currentTreeUB } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 74db8151a5..8d586b6435 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -123,7 +123,7 @@ trait L1FunctionCallInterpreter } else if (callState.returnDependees.contains(m)) { StringTreeOr(callState.returnDependees(m).map { rd => if (rd.hasUBP) { - rd.ub.sci.tree.replaceParameters(parameters.map { kv => (kv._1, env(pc, kv._2)) }) + rd.ub.tree.replaceParameters(parameters.map { kv => (kv._1, env(pc, kv._2)) }) } else StringTreeNode.ub }) } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 818404f02d..1517863c14 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -155,7 +155,7 @@ class L3FieldReadInterpreter( } else { var trees = accessState.accessDependees.map { ad => if (ad.hasUBP) { - val tree = ad.ub.sci.tree + val tree = ad.ub.tree if (tree.parameterIndices.nonEmpty) { // We cannot handle write values that contain parameter indices since resolving the parameters // requires context and this interpreter is present in multiple contexts. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index 5ac6bb4aa7..753d2b88d7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -14,7 +14,6 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.properties.string.StringConstancyInformation import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.br.fpcf.properties.string.StringTreeNode @@ -81,7 +80,7 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly ) } else if (state.tacDependee.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - Result(state.entity, StringConstancyProperty(StringConstancyInformation(failure))) + Result(state.entity, StringConstancyProperty(failure)) } else { determinePossibleStrings } @@ -122,7 +121,7 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly mapDefPCToStringTree(state.entity.pc) } - Result(state.entity, StringConstancyProperty(StringConstancyInformation(tree))) + Result(state.entity, StringConstancyProperty(tree)) } private def failure: StringTreeNode = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala index 49f3944452..96d3417eaf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala @@ -102,7 +102,7 @@ class SystemPropertiesAnalysis private[analyses] ( case eps @ EUBP(_: VariableContext, ub: StringConstancyProperty) => state.updateStringDependee(eps.asInstanceOf[EPS[VariableContext, StringConstancyProperty]]) - returnResults(Set(ub.sci.tree))(state) + returnResults(Set(ub.tree))(state) case _ => throw new IllegalArgumentException(s"unexpected eps $eps") @@ -116,7 +116,7 @@ class SystemPropertiesAnalysis private[analyses] ( ) match { case eps @ UBP(ub) => state.updateStringDependee(eps) - Set(ub.sci.tree) + Set(ub.tree) case epk: EOptionP[VariableContext, StringConstancyProperty] => state.updateStringDependee(epk) From 7517af8e74f1d3464e76f08a9e25458e445804d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 23:07:57 +0200 Subject: [PATCH 549/583] Remove unused string constancy type --- .../string/StringConstancyType.scala | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala deleted file mode 100644 index 25bfd0726b..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyType.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package fpcf -package properties -package string - -/** - * Values in this enumeration represent how a string / string container, such as [[StringBuilder]], - * are changed. - * - * @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.setLength(0)`). - */ - 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") - -} From 586821a0b04138879c44c90476ed8c01a19ebb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 23:25:42 +0200 Subject: [PATCH 550/583] Convert string constancy level from enum to case objects --- .../info/StringAnalysisReflectiveCalls.scala | 6 +- .../string_analysis/StringMatcher.scala | 8 +-- .../string/StringConstancyLevel.scala | 58 +++++++++---------- .../properties/string/StringTreeNode.scala | 22 +++---- .../StringConstancyLevelTests.scala | 42 +++++--------- .../L1VirtualFunctionCallInterpreter.scala | 2 +- 6 files changed, 61 insertions(+), 77 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 0ea6cf63ac..60d3fdf7f0 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 @@ -332,9 +332,9 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { 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 + case StringConstancyLevel.Constant => constantCount += 1 + case StringConstancyLevel.PartiallyConstant => partConstantCount += 1 + case StringConstancyLevel.Dynamic => dynamicCount += 1 } } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala index 48399a7126..200ce56f6f 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala @@ -30,7 +30,7 @@ sealed trait StringMatcher extends AbstractPropertyMatcher { } } -sealed abstract class ConstancyStringMatcher(val constancyLevel: StringConstancyLevel.Value) extends StringMatcher { +sealed abstract class ConstancyStringMatcher(val constancyLevel: StringConstancyLevel) extends StringMatcher { override def validateProperty( p: Project[_], @@ -60,9 +60,9 @@ sealed abstract class ConstancyStringMatcher(val constancyLevel: StringConstancy } } -class ConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.CONSTANT) -class PartiallyConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.PARTIALLY_CONSTANT) -class DynamicStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.DYNAMIC) +class ConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.Constant) +class PartiallyConstantStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.PartiallyConstant) +class DynamicStringMatcher extends ConstancyStringMatcher(StringConstancyLevel.Dynamic) class InvalidStringMatcher extends StringMatcher { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index dc96484109..a9acbdd949 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -6,36 +6,30 @@ package properties package string /** - * Values in this enumeration represent the granularity of used strings. - * - * @author Patrick Mell + * @author Maximilian Rüsch */ -object StringConstancyLevel extends Enumeration { +sealed trait StringConstancyLevel - type StringConstancyLevel = StringConstancyLevel.Value +object StringConstancyLevel { /** - * This level indicates that a string has a constant value at a given read operation. + * Indicates that a string has a constant value at a given read operation. */ - final val CONSTANT = Value("constant") + case object Constant extends StringConstancyLevel /** - * 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. + * Indicates that a string is partially constant (has a constant and a dynamic part) at some read operation. */ - final val PARTIALLY_CONSTANT = Value("partially_constant") + case object PartiallyConstant extends StringConstancyLevel /** - * This level indicates that a string at some read operations has an unpredictable value. + * Indicates that a string at some read operations has an unpredictable value. */ - final val DYNAMIC = Value("dynamic") + case object Dynamic extends StringConstancyLevel /** - * Returns the more general StringConstancyLevel of the two given levels. DYNAMIC is more - * general than PARTIALLY_CONSTANT which is more general than CONSTANT. + * The more general StringConstancyLevel of the two given levels, i.e. the one that allows more possible + * values at the given read operation. * * @param level1 The first level. * @param level2 The second level. @@ -45,21 +39,23 @@ object StringConstancyLevel extends Enumeration { level1: StringConstancyLevel, level2: StringConstancyLevel ): StringConstancyLevel = { - if (level1 == DYNAMIC || level2 == DYNAMIC) { - DYNAMIC - } else if (level1 == PARTIALLY_CONSTANT || level2 == PARTIALLY_CONSTANT) { - PARTIALLY_CONSTANT + if (level1 == Dynamic || level2 == Dynamic) { + Dynamic + } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { + PartiallyConstant } else { - CONSTANT + 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 + *

      + *
    • Constant + Constant = Constant
    • + *
    • Dynamic + Dynamic = Dynamic
    • + *
    • Constant + Dynamic = PartiallyConstant
    • + *
    • PartiallyConstant + {Dynamic, Constant} = PartiallyConstant
    • + *
    * * @param level1 The first level. * @param level2 The second level. @@ -69,12 +65,10 @@ object StringConstancyLevel extends Enumeration { 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 + if (level1 == PartiallyConstant || level2 == PartiallyConstant) { + PartiallyConstant + } else if ((level1 == Constant && level2 == Dynamic) || (level1 == Dynamic && level2 == Constant)) { + PartiallyConstant } else { level1 } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index d2b7ad09e3..a79c2e9ded 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -38,7 +38,7 @@ sealed trait StringTreeNode { def simplify: StringTreeNode - def constancyLevel: StringConstancyLevel.Value + def constancyLevel: StringConstancyLevel lazy val parameterIndices: Set[Int] = children.flatMap(_.parameterIndices).toSet final def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { @@ -107,7 +107,7 @@ case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNod StringTreeRepetition(simplifiedChild) } - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic override lazy val parameterIndices: Set[Int] = child.parameterIndices @@ -153,7 +153,7 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } } - override def constancyLevel: StringConstancyLevel.Value = + override def constancyLevel: StringConstancyLevel = children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineForConcat) def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { @@ -203,7 +203,7 @@ trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { } } - override def constancyLevel: StringConstancyLevel.Value = + override def constancyLevel: StringConstancyLevel = _children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) override lazy val parameterIndices: Set[Int] = _children.flatMap(_.parameterIndices).toSet @@ -367,7 +367,7 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { case class StringTreeConst(string: String) extends SimpleStringTreeNode { override def _toRegex: String = Regex.quoteReplacement(string).replaceAll("\\[", "\\\\[") - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant def isIntConst: Boolean = Try(string.toInt).isSuccess @@ -387,7 +387,7 @@ case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = parameters.getOrElse(index, this) - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } object StringTreeParameter { @@ -404,7 +404,7 @@ object StringTreeParameter { object StringTreeInvalidElement extends SimpleStringTreeNode { override def _toRegex: String = throw new UnsupportedOperationException() - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant override def isInvalid: Boolean = true } @@ -413,23 +413,23 @@ object StringTreeNull extends SimpleStringTreeNode { // Using this element nested in some other element might lead to unexpected results... override def _toRegex: String = "^null$" - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.CONSTANT + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant } object StringTreeDynamicString extends SimpleStringTreeNode { override def _toRegex: String = ".*" - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } object StringTreeDynamicInt extends SimpleStringTreeNode { override def _toRegex: String = "^-?\\d+$" - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } object StringTreeDynamicFloat extends SimpleStringTreeNode { override def _toRegex: String = "^-?\\d*\\.{0,1}\\d+$" - override def constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } 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 68d2e1f796..8d55320cc6 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 @@ -6,44 +6,34 @@ package string_definition import org.scalatest.funsuite.AnyFunSuite import org.opalj.br.fpcf.properties.string.StringConstancyLevel -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.CONSTANT -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.DYNAMIC -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.PARTIALLY_CONSTANT +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Constant +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Dynamic +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.PartiallyConstant /** * Tests for [[StringConstancyLevel]] methods. * - * @author Patrick Mell + * @author Maximilian Rüsch */ @org.junit.runner.RunWith(classOf[org.scalatestplus.junit.JUnitRunner]) class StringConstancyLevelTests extends AnyFunSuite { 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) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Dynamic) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, PartiallyConstant) == PartiallyConstant) + 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) + // 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) + // { Dynamic, PartiallyConstant } + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Dynamic) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, PartiallyConstant) == Dynamic) - // { PARTIALLY_CONSTANT, CONSTANT } - assert(StringConstancyLevel.determineMoreGeneral( - PARTIALLY_CONSTANT, - CONSTANT - ) == PARTIALLY_CONSTANT) - assert(StringConstancyLevel.determineMoreGeneral( - CONSTANT, - PARTIALLY_CONSTANT - ) == PARTIALLY_CONSTANT) + // { PartiallyConstant, Constant } + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Constant) == PartiallyConstant) + assert(StringConstancyLevel.determineMoreGeneral(Constant, PartiallyConstant) == PartiallyConstant) } - } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 5657aa2472..97413f6a3c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -140,7 +140,7 @@ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringI valueState } case ComputationalTypeFloat | ComputationalTypeDouble => - if (valueState.constancyLevel == StringConstancyLevel.CONSTANT) { + if (valueState.constancyLevel == StringConstancyLevel.Constant) { valueState } else { if (soundnessMode.isHigh) StringTreeDynamicFloat From f5069184c6288c4923fc02a4709ac35c2adaaa74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Sep 2024 23:26:25 +0200 Subject: [PATCH 551/583] Remove unused string tree repetition --- .../properties/string/StringTreeNode.scala | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index a79c2e9ded..9db03ddfa5 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -89,39 +89,6 @@ object StringTreeNode { def ub: StringTreeNode = StringTreeInvalidElement } -case class StringTreeRepetition(child: StringTreeNode) extends CachedSimplifyNode with CachedHashCode { - - override val children: Seq[StringTreeNode] = Seq(child) - - override def _toRegex: String = s"(${child.toRegex})*" - - override def sorted: StringTreeNode = this - - override def _simplify: StringTreeNode = { - val simplifiedChild = child.simplify - if (simplifiedChild.isInvalid) - StringTreeInvalidElement - else if (simplifiedChild.isEmpty) - StringTreeEmptyConst - else - StringTreeRepetition(simplifiedChild) - } - - override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic - - override lazy val parameterIndices: Set[Int] = child.parameterIndices - - def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = - StringTreeRepetition(child.replaceParameters(parameters)) - - def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { - if (targetDepth == 1) - replacement - else - StringTreeRepetition(child.limitToDepth(targetDepth - 1, replacement)) - } -} - case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { override def _toRegex: String = { From 506f09637b2204fcc176d86c6897b784de94eada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 10:27:46 +0200 Subject: [PATCH 552/583] Fix recursion depth limit barrier --- .../org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 69abba5210..ff991e5049 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -80,7 +80,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec val newProperty = StringConstancyProperty(state.stringFlowDependee match { case UBP(methodStringFlow) => val tree = methodStringFlow(state.entity.pc, state.entity.pv) - if (tree.depth == maxDepth) { + if (tree.depth >= maxDepth) { // String constancy information got too complex, abort. This guard can probably be removed once // recursing functions are properly handled using e.g. the widen-converge approach. state.hitMaximumDepth = true From 76d52103f924e83a2338df32b744ddb0abd135c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 10:28:03 +0200 Subject: [PATCH 553/583] Revert unwanted changes in class usage analysis --- .../support/info/ClassUsageAnalysis.scala | 95 +++++++++---------- 1 file changed, 44 insertions(+), 51 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 335d8caea2..d62ca9e0bd 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 @@ -6,7 +6,8 @@ package info import scala.annotation.switch import java.net.URL -import scala.collection.mutable +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable.ListBuffer import org.opalj.br.analyses.BasicReport @@ -16,10 +17,13 @@ import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.log.GlobalLogContext import org.opalj.tac.Assignment import org.opalj.tac.Call -import org.opalj.tac.EagerDetachedTACAIKey +import org.opalj.tac.DUVar import org.opalj.tac.ExprStmt -import org.opalj.tac.V -import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.LazyDetachedTACAIKey +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.StaticMethodCall +import org.opalj.tac.VirtualMethodCall +import org.opalj.value.ValueInformation /** * Analyzes a project for how a particular class is used within that project. Collects information @@ -31,41 +35,28 @@ import org.opalj.tac.VirtualFunctionCall * [[ClassUsageAnalysis.analysisSpecificParametersDescription]]. * * @author Patrick Mell + * @author Dominik Helm */ object ClassUsageAnalysis extends ProjectAnalysisApplication { + private type V = DUVar[ValueInformation] + implicit val logContext: GlobalLogContext.type = GlobalLogContext override def title: String = "Class Usage Analysis" override def description: String = { - "Analysis a project for how a particular class is used, i.e., which methods are called " + - "on it" + "Analyzes a project for how a particular class is used within it, i.e., which methods " + + "of instances of that class are called" } - /** - * 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 = false - /** * 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]): String = { + private def assembleMethodDescriptor(call: Call[V], isFineGrainedAnalysis: Boolean): String = { val fqMethodName = s"${call.declaringClass.toJava}#${call.name}" if (isFineGrainedAnalysis) { val methodDescriptor = call.descriptor.toString @@ -76,18 +67,21 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { } /** - * 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]]. + * 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]]. */ - private def processFunctionCall(call: Call[V], map: mutable.Map[String, Int]): Unit = { + private def processCall( + call: Call[V], + map: ConcurrentHashMap[String, AtomicInteger], + className: String, + isFineGrainedAnalysis: Boolean + ): Unit = { val declaringClassName = call.declaringClass.toJava if (declaringClassName == className) { - val methodDescriptor = assembleMethodDescriptor(call) - if (map.contains(methodDescriptor)) { - map.put(methodDescriptor, 1) - } else { - map.update(methodDescriptor, map(methodDescriptor) + 1) + val methodDescriptor = assembleMethodDescriptor(call, isFineGrainedAnalysis) + if (map.putIfAbsent(methodDescriptor, new AtomicInteger(1)) != null) { + map.get(methodDescriptor).addAndGet(1) } } } @@ -144,8 +138,8 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { } else { false // default is coarse grained } - (className, isFineGrainedAnalysis) + (className, isFineGrainedAnalysis) } override def doAnalyze( @@ -153,33 +147,32 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { - getAnalysisParameters(parameters) - val resultMap = mutable.Map[String, Int]() - val tacProvider = project.get(EagerDetachedTACAIKey) + val (className, isFineGrainedAnalysis) = getAnalysisParameters(parameters) + val resultMap: ConcurrentHashMap[String, AtomicInteger] = new ConcurrentHashMap() + val tacProvider = project.get(LazyDetachedTACAIKey) - project.allMethodsWithBody.foreach { m => - tacProvider(m).stmts.foreach { stmt => + project.parForeachMethodWithBody() { methodInfo => + tacProvider(methodInfo.method).stmts.foreach { stmt => (stmt.astID: @switch) match { - 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 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 _ => } } } - 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" - }) + 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}")) BasicReport(report) } From abc228d49cfac8be539e53344b36ae96ebbd81b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 10:39:52 +0200 Subject: [PATCH 554/583] Unify handling of soundness mode --- .../org/opalj/fpcf/StringAnalysisTest.scala | 7 ++- OPAL/tac/src/main/resources/reference.conf | 17 +++---- .../fpcf/analyses/string/StringAnalysis.scala | 2 +- .../string/UniversalStringConfig.scala | 44 +++++++++++++++++++ .../MethodStringFlowAnalysis.scala | 19 +------- .../InterpretationHandler.scala | 25 +---------- .../trivial/TrivialStringAnalysis.scala | 28 +----------- 7 files changed, 56 insertions(+), 86 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala 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 d5301f81ec..04ce59759d 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 @@ -50,9 +50,9 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.StringAnalysis +import org.opalj.tac.fpcf.analyses.string.UniversalStringConfig import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis @@ -76,13 +76,12 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } super.createConfig() - .withValue(InterpretationHandler.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue(UniversalStringConfig.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue(StringAnalysis.MaxDepthConfigKey, ConfigValueFactory.fromAnyRef(30)) .withValue( MethodStringFlowAnalysis.ExcludedPackagesConfigKey, ConfigValueFactory.fromIterable(new util.ArrayList[String]()) ) - .withValue(MethodStringFlowAnalysis.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) - .withValue(StringAnalysis.MaxDepthConfigKey, ConfigValueFactory.fromAnyRef(30)) } override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string") diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index b4a005f3fd..edb1fb5814 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1531,18 +1531,11 @@ org.opalj { cg.reflection.ReflectionRelatedCallsAnalysis.highSoundness = "" // e.g. "all" or "class,method", fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, string { - InterpretationHandler.highSoundness = false, - MethodStringFlowAnalysis { - excludedPackages = [ - "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets - ], - highSoundness = false - }, - StringAnalysis { - highSoundness = false, - maxDepth = 10 - }, - TrivialStringAnalysis.highSoundness = false + highSoundness = false, + StringAnalysis.maxDepth = 10 + MethodStringFlowAnalysis.excludedPackages = [ + "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets + ] } } }, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index ff991e5049..8092bfd0de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -33,7 +33,7 @@ import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow -trait StringAnalysis extends FPCFAnalysis { +trait StringAnalysis extends FPCFAnalysis with UniversalStringConfig { private final val ConfigLogCategory = "analysis configuration - string analysis" diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala new file mode 100644 index 0000000000..d29952a27b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala @@ -0,0 +1,44 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string + +import org.opalj.br.analyses.SomeProject +import org.opalj.log.Error +import org.opalj.log.Info +import org.opalj.log.LogContext +import org.opalj.log.OPALLogger.logOnce + +/** + * @author Maximilian Rüsch + */ +trait UniversalStringConfig { + + val project: SomeProject + implicit def logContext: LogContext + + private final val ConfigLogCategory = "analysis configuration - string analysis - universal" + + implicit val soundnessMode: SoundnessMode = { + val mode = + try { + SoundnessMode(project.config.getBoolean(UniversalStringConfig.SoundnessModeConfigKey)) + } catch { + case t: Throwable => + logOnce { + Error(ConfigLogCategory, s"couldn't read: ${UniversalStringConfig.SoundnessModeConfigKey}", t) + } + SoundnessMode(false) + } + + logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) + mode + } +} + +object UniversalStringConfig { + + final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.highSoundness" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index 64b193bd50..17181cebd0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -31,7 +31,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch */ -class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis { +class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis with UniversalStringConfig { private final val ConfigLogCategory = "analysis configuration - method string flow analysis" @@ -55,22 +55,6 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn packages.toSeq } - private val soundnessMode: SoundnessMode = { - val mode = - try { - SoundnessMode(project.config.getBoolean(MethodStringFlowAnalysis.SoundnessModeConfigKey)) - } catch { - case t: Throwable => - logOnce { - Error(ConfigLogCategory, s"couldn't read: ${MethodStringFlowAnalysis.SoundnessModeConfigKey}", t) - } - SoundnessMode(false) - } - - logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) - mode - } - val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) def analyze(method: Method): ProperPropertyComputationResult = { @@ -157,5 +141,4 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn object MethodStringFlowAnalysis { final val ExcludedPackagesConfigKey = "org.opalj.fpcf.analyses.string.MethodStringFlowAnalysis.excludedPackages" - final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.MethodStringFlowAnalysis.highSoundness" } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 5f887566bf..ec28f8bff8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -14,9 +14,6 @@ import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.SomeEPS -import org.opalj.log.Error -import org.opalj.log.Info -import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -29,25 +26,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * * @author Maximilian Rüsch */ -abstract class InterpretationHandler extends FPCFAnalysis { - - private final val ConfigLogCategory = "analysis configuration - string flow interpretation analysis" - - implicit val soundnessMode: SoundnessMode = { - val mode = - try { - SoundnessMode(project.config.getBoolean(InterpretationHandler.SoundnessModeConfigKey)) - } catch { - case t: Throwable => - logOnce { - Error(ConfigLogCategory, s"couldn't read: ${InterpretationHandler.SoundnessModeConfigKey}", t) - } - SoundnessMode(false) - } - - logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) - mode - } +abstract class InterpretationHandler extends FPCFAnalysis with UniversalStringConfig { def analyze(entity: MethodPC): ProperPropertyComputationResult = { val tacaiEOptP = ps(entity.dm.definedMethod, TACAI.key) @@ -100,8 +79,6 @@ abstract class InterpretationHandler extends FPCFAnalysis { object InterpretationHandler { - final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.InterpretationHandler.highSoundness" - def getEntity(implicit state: InterpretationState): MethodPC = MethodPC(state.pc, state.dm) /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index 753d2b88d7..08f6d9ef74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -26,9 +26,6 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.log.Error -import org.opalj.log.Info -import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI /** @@ -42,25 +39,7 @@ import org.opalj.tac.fpcf.properties.TACAI * * @see [[StringAnalysis]] */ -class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis { - - private final val ConfigLogCategory = "analysis configuration - trivial string analysis" - - private val soundnessMode: SoundnessMode = { - val mode = - try { - SoundnessMode(project.config.getBoolean(TrivialStringAnalysis.SoundnessModeConfigKey)) - } catch { - case t: Throwable => - logOnce { - Error(ConfigLogCategory, s"couldn't read: ${TrivialStringAnalysis.SoundnessModeConfigKey}", t) - } - SoundnessMode(false) - } - - logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) - mode - } +class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis with UniversalStringConfig { private case class TrivialStringAnalysisState(entity: VariableContext, var tacDependee: EOptionP[Method, TACAI]) @@ -130,11 +109,6 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly } } -private object TrivialStringAnalysis { - - private final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.TrivialStringAnalysis.highSoundness" -} - /** * @author Maximilian Rüsch * From b509566f9e9469c6dc5ae09c1f2bf9c38010df71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 12:48:50 +0200 Subject: [PATCH 555/583] Refactor max depth to depth threshold --- .../org/opalj/fpcf/StringAnalysisTest.scala | 2 +- OPAL/tac/src/main/resources/reference.conf | 2 +- .../fpcf/analyses/string/StringAnalysis.scala | 41 +++++++++++++------ .../analyses/string/StringAnalysisState.scala | 2 +- 4 files changed, 31 insertions(+), 16 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 04ce59759d..db1dd38975 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 @@ -77,7 +77,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { super.createConfig() .withValue(UniversalStringConfig.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) - .withValue(StringAnalysis.MaxDepthConfigKey, ConfigValueFactory.fromAnyRef(30)) + .withValue(StringAnalysis.DepthThresholdConfigKey, ConfigValueFactory.fromAnyRef(10)) .withValue( MethodStringFlowAnalysis.ExcludedPackagesConfigKey, ConfigValueFactory.fromIterable(new util.ArrayList[String]()) diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index edb1fb5814..379abb0515 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1532,7 +1532,7 @@ org.opalj { fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysis.highSoundness = false, string { highSoundness = false, - StringAnalysis.maxDepth = 10 + StringAnalysis.depthThreshold = 10 MethodStringFlowAnalysis.excludedPackages = [ "sun.nio.cs" // Due to problems with the size of the static initialization in the charsets ] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 8092bfd0de..2587438c0d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -37,24 +37,31 @@ trait StringAnalysis extends FPCFAnalysis with UniversalStringConfig { private final val ConfigLogCategory = "analysis configuration - string analysis" - protected val maxDepth: Int = { - val maxDepth = + /** + * @see [[StringAnalysis.DepthThresholdConfigKey]] + */ + protected val depthThreshold: Int = { + val depthThreshold = try { - project.config.getInt(StringAnalysis.MaxDepthConfigKey) + project.config.getInt(StringAnalysis.DepthThresholdConfigKey) } catch { case t: Throwable => - logOnce(Error(ConfigLogCategory, s"couldn't read: ${StringAnalysis.MaxDepthConfigKey}", t)) + logOnce(Error(ConfigLogCategory, s"couldn't read: ${StringAnalysis.DepthThresholdConfigKey}", t)) 30 } - logOnce(Info(ConfigLogCategory, "using maximum depth " + maxDepth)) - maxDepth + logOnce(Info(ConfigLogCategory, "using depth threshold " + depthThreshold)) + depthThreshold } } object StringAnalysis { - final val MaxDepthConfigKey = "org.opalj.fpcf.analyses.string.StringAnalysis.maxDepth" + /** + * The string tree depth after which the string analysis does not continue and returns either the current tree + * or the tree limited to the depth threshold, depending on the soundness mode in use. + */ + final val DepthThresholdConfigKey = "org.opalj.fpcf.analyses.string.StringAnalysis.depthThreshold" } /** @@ -79,20 +86,28 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec private def computeResults(implicit state: ContextFreeStringAnalysisState): ProperPropertyComputationResult = { val newProperty = StringConstancyProperty(state.stringFlowDependee match { case UBP(methodStringFlow) => - val tree = methodStringFlow(state.entity.pc, state.entity.pv) - if (tree.depth >= maxDepth) { + val tree = methodStringFlow(state.entity.pc, state.entity.pv).simplify + if (tree.depth >= depthThreshold) { // String constancy information got too complex, abort. This guard can probably be removed once // recursing functions are properly handled using e.g. the widen-converge approach. - state.hitMaximumDepth = true - tree.limitToDepth(maxDepth, StringTreeNode.lb) + state.hitDepthThreshold = true + if (soundnessMode.isHigh) { + tree.limitToDepth(depthThreshold, StringTreeNode.lb) + } else { + // In low soundness, we cannot decrease the matched string values by limiting the string tree + // with the upper bound. We should also not limit it with the lower bound, since that would + // make the string tree at least partially dynamic and cause other low soundness analysis to + // abort. Thus, return the tree itself as the final value. + tree + } } else { - tree.simplify + tree } case _: EPK[_, MethodStringFlow] => StringTreeNode.ub }) - if (state.hasDependees && !state.hitMaximumDepth) { + if (state.hasDependees && !state.hitDepthThreshold) { InterimResult( state.entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 4b2232f8d1..c61e16c506 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -26,7 +26,7 @@ import org.opalj.tac.fpcf.properties.string.MethodStringFlow private[string] case class ContextFreeStringAnalysisState( entity: VariableDefinition, var stringFlowDependee: EOptionP[Method, MethodStringFlow], - var hitMaximumDepth: Boolean = false + var hitDepthThreshold: Boolean = false ) { def hasDependees: Boolean = stringFlowDependee.isRefinable From 455df95d983f8d147bbe9ed9cb04831a7c4e18cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 12:49:30 +0200 Subject: [PATCH 556/583] Remove some instability by hardening method parameter handling for field reads --- .../l3/interpretation/L3FieldReadInterpreter.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 1517863c14..baa40f7ffd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -133,6 +133,8 @@ class L3FieldReadInterpreter( // Field parameter information is not available accessState.hasUnresolvableAccess = true } else { + // IMPROVE use variable contexts here to support field writes based on method parameters in other + // methods val entity = VariableDefinition(pc, PUVar(parameter.get._1, parameter.get._2), method) accessState.accessDependees = accessState.accessDependees :+ ps(entity, StringConstancyProperty.key) } @@ -159,8 +161,13 @@ class L3FieldReadInterpreter( if (tree.parameterIndices.nonEmpty) { // We cannot handle write values that contain parameter indices since resolving the parameters // requires context and this interpreter is present in multiple contexts. - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub + tree.replaceParameters(tree.parameterIndices.map { paramIndex => + ( + paramIndex, + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub + ) + }.toMap) } else tree } else StringTreeNode.ub From 395f7387b7ed4507c8d8a5ef9df81b606b098855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 22:52:48 +0200 Subject: [PATCH 557/583] Add improvement comment for joining environments --- .../tac/fpcf/properties/string/StringTreeEnvironment.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index f1e12b27e3..8ce92422eb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -80,6 +80,9 @@ object StringTreeEnvironment { } def joinMany(envs: Iterable[StringTreeEnvironment]): StringTreeEnvironment = { + // IMPROVE as environments get larger (with growing variable count in a method) joining them impacts performance + // especially when analysing flow through proper regions. This could be improved by detecting and joining only + // changes between multiple environments, e.g. with linked lists. envs.size match { case 0 => throw new IllegalArgumentException("Cannot join zero environments!") case 1 => envs.head From 4ff5439a71bcf38fcb21ba59e5ed2f8b1729d583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 22:53:22 +0200 Subject: [PATCH 558/583] Fix inefficiency for computing new upper bounds for field read trees --- .../L3FieldReadInterpreter.scala | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index baa40f7ffd..baac0be6b2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -90,7 +90,6 @@ class L3FieldReadInterpreter( if (fieldAccessEOptP.hasUBP) { handleFieldAccessInformation(fieldAccessEOptP.ub) } else { - accessState.previousResults.prepend(StringTreeNode.ub) InterimResult.forUB( InterpretationHandler.getEntity, StringFlowFunctionProperty.ub(state.pc, target), @@ -107,11 +106,15 @@ class L3FieldReadInterpreter( ): ProperPropertyComputationResult = { if (accessState.fieldAccessDependee.isFinal && accessInformation.accesses.isEmpty) { // No methods which write the field were found => Field could either be null or any value - return computeFinalResult(computeUBWithNewTree(StringTreeOr.fromNodes( - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub, - StringTreeNull - ))) + return computeFinalResult(StringFlowFunctionProperty.constForVariableAt( + state.pc, + accessState.target, + StringTreeOr.fromNodes( + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub, + StringTreeNull + ) + )) } accessInformation.getNewestAccesses( @@ -134,7 +137,7 @@ class L3FieldReadInterpreter( accessState.hasUnresolvableAccess = true } else { // IMPROVE use variable contexts here to support field writes based on method parameters in other - // methods + // methods. Requires a context to exist for variable definitions as well val entity = VariableDefinition(pc, PUVar(parameter.get._1, parameter.get._2), method) accessState.accessDependees = accessState.accessDependees :+ ps(entity, StringConstancyProperty.key) } @@ -153,7 +156,7 @@ class L3FieldReadInterpreter( if (accessState.hasWriteInSameMethod && soundnessMode.isHigh) { // We cannot handle writes to a field that is read in the same method at the moment as the flow functions do // not capture field state. This can be improved upon in the future. - computeFinalResult(computeUBWithNewTree(StringTreeNode.lb)) + computeFinalResult(StringFlowFunctionProperty.lb(state.pc, accessState.target)) } else { var trees = accessState.accessDependees.map { ad => if (ad.hasUBP) { @@ -183,15 +186,16 @@ class L3FieldReadInterpreter( trees = trees :+ StringTreeNode.lb } + val newUB = StringFlowFunctionProperty.constForVariableAt(state.pc, accessState.target, StringTreeOr(trees)) if (accessState.hasDependees) { InterimResult.forUB( InterpretationHandler.getEntity, - computeUBWithNewTree(StringTreeOr(trees)), + newUB, accessState.dependees.toSet, continuation(accessState, state) ) } else { - computeFinalResult(computeUBWithNewTree(StringTreeOr(trees))) + computeFinalResult(newUB) } } } @@ -212,18 +216,4 @@ class L3FieldReadInterpreter( case _ => throw new IllegalArgumentException(s"Encountered unknown eps: $eps") } } - - private def computeUBWithNewTree(newTree: StringTreeNode)( - implicit - accessState: FieldReadState, - state: InterpretationState - ): StringFlowFunctionProperty = { - accessState.previousResults.prepend(newTree) - - StringFlowFunctionProperty.constForVariableAt( - state.pc, - accessState.target, - StringTreeOr(accessState.previousResults.toSeq) - ) - } } From 5452078068486d2433cc9bb4211fb643c63b6ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 22:59:34 +0200 Subject: [PATCH 559/583] Allow string constants to be resolved outside of method string flow --- .../fpcf/analyses/string/StringAnalysis.scala | 97 ++++++++++++++++--- .../analyses/string/StringAnalysisState.scala | 10 +- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 2587438c0d..b331d41731 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -9,6 +9,8 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.DeclaredMethod import org.opalj.br.Method +import org.opalj.br.PDVar +import org.opalj.br.PUVar import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis @@ -17,7 +19,9 @@ import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.NoCallers import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.br.fpcf.properties.string.StringTreeNode +import org.opalj.br.fpcf.properties.string.StringTreeOr import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.EPS @@ -69,13 +73,32 @@ object StringAnalysis { */ private[string] class ContextFreeStringAnalysis(override val project: SomeProject) extends StringAnalysis { - def analyze(vd: VariableDefinition): ProperPropertyComputationResult = - computeResults(ContextFreeStringAnalysisState(vd, ps(vd.m, MethodStringFlow.key))) + def analyze(vd: VariableDefinition): ProperPropertyComputationResult = { + implicit val state: ContextFreeStringAnalysisState = ContextFreeStringAnalysisState(vd, ps(vd.m, TACAI.key)) + + if (state.tacaiDependee.isRefinable) { + InterimResult.forUB( + state.entity, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + } else if (state.tacaiDependee.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + Result(state.entity, StringConstancyProperty.lb) + } else { + computeResults + } + } private def continuation(state: ContextFreeStringAnalysisState)(eps: SomeEPS): ProperPropertyComputationResult = { eps match { + case _ if eps.pk == TACAI.key => + state.tacaiDependee = eps.asInstanceOf[EOptionP[Method, TACAI]] + computeResults(state) + case _ if eps.pk == MethodStringFlow.key => - state.stringFlowDependee = eps.asInstanceOf[EOptionP[Method, MethodStringFlow]] + state.stringFlowDependee = Some(eps.asInstanceOf[EOptionP[Method, MethodStringFlow]]) computeResults(state) case _ => @@ -84,7 +107,61 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec } private def computeResults(implicit state: ContextFreeStringAnalysisState): ProperPropertyComputationResult = { - val newProperty = StringConstancyProperty(state.stringFlowDependee match { + val newUB = StringConstancyProperty( + if (state.stringFlowDependee.isEmpty) computeUBTreeUsingTACAI + else computeUBTreeUsingStringFlow + ) + + if (state.hasDependees && !state.hitDepthThreshold) { + InterimResult( + state.entity, + StringConstancyProperty.lb, + newUB, + state.dependees, + continuation(state) + ) + } else { + Result(state.entity, newUB) + } + } + + private def computeUBTreeUsingTACAI(implicit state: ContextFreeStringAnalysisState): StringTreeNode = { + val tac = state.tacaiDependee.ub.tac.get + + def mapDefPCToStringTree(defPC: Int): Option[StringTreeNode] = { + if (defPC < 0) { + None + } else { + tac.stmts(valueOriginOfPC(defPC, tac.pcToIndex).get).asAssignment.expr match { + case StringConst(_, v) => Some(StringTreeConst(v)) + case _ => None + } + } + } + + val treeOpts = state.entity.pv match { + case PUVar(_, defPCs) => + defPCs.map(pc => mapDefPCToStringTree(pc)) + + case PDVar(_, _) => + Set(mapDefPCToStringTree(state.entity.pc)) + } + + if (treeOpts.exists(_.isEmpty)) { + // Could not resolve all values immediately (i.e. non-literal strings), so revert to computing string flow + state.stringFlowDependee = Some(ps(state.entity.m, MethodStringFlow.key)) + computeUBTreeUsingStringFlow + } else { + StringTreeOr(treeOpts.map(_.get)) + } + } + + private def computeUBTreeUsingStringFlow(implicit state: ContextFreeStringAnalysisState): StringTreeNode = { + if (state.stringFlowDependee.isEmpty) { + throw new IllegalStateException(s"Requested to compute an UB using method string flow but none is given!") + } + + state.stringFlowDependee.get match { case UBP(methodStringFlow) => val tree = methodStringFlow(state.entity.pc, state.entity.pv).simplify if (tree.depth >= depthThreshold) { @@ -105,18 +182,6 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec } case _: EPK[_, MethodStringFlow] => StringTreeNode.ub - }) - - if (state.hasDependees && !state.hitDepthThreshold) { - InterimResult( - state.entity, - StringConstancyProperty.lb, - newProperty, - state.dependees, - continuation(state) - ) - } else { - Result(state.entity, newProperty) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index c61e16c506..d139ffce2c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -25,13 +25,15 @@ import org.opalj.tac.fpcf.properties.string.MethodStringFlow private[string] case class ContextFreeStringAnalysisState( entity: VariableDefinition, - var stringFlowDependee: EOptionP[Method, MethodStringFlow], - var hitDepthThreshold: Boolean = false + var tacaiDependee: EOptionP[Method, TACAI], + var stringFlowDependee: Option[EOptionP[Method, MethodStringFlow]] = None, + var hitDepthThreshold: Boolean = false ) { - def hasDependees: Boolean = stringFlowDependee.isRefinable + def hasDependees: Boolean = tacaiDependee.isRefinable || stringFlowDependee.exists(_.isRefinable) - def dependees: Set[EOptionP[Entity, Property]] = Set(stringFlowDependee) + def dependees: Set[EOptionP[Entity, Property]] = + Set(tacaiDependee).filter(_.isRefinable) ++ stringFlowDependee.filter(_.isRefinable) } private[string] class ContextStringAnalysisState private ( From 3e43f08884dc4d3759f8f87cc6796a2acf80ad19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 23:06:04 +0200 Subject: [PATCH 560/583] Simplify string analysis state definition --- .../analyses/string/StringAnalysisState.scala | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index d139ffce2c..c26dfece74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -37,19 +37,15 @@ private[string] case class ContextFreeStringAnalysisState( } private[string] class ContextStringAnalysisState private ( - _entity: VariableContext, - var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty], + val entity: VariableContext, + private var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty], private var _parameterIndices: Set[Int] ) { - def entity: VariableContext = _entity - def dm: DeclaredMethod = _entity.context.method - def updateStringDependee(stringDependee: EPS[VariableDefinition, StringConstancyProperty]): Unit = { _stringDependee = stringDependee _parameterIndices ++= stringDependee.ub.tree.parameterIndices } - private def stringTree: StringTreeNode = _stringDependee.ub.tree def parameterIndices: Set[Int] = _parameterIndices // Parameter StringConstancy @@ -79,7 +75,7 @@ private[string] class ContextStringAnalysisState private ( ) }.toMap - stringTree.replaceParameters(paramTrees).simplify + _stringDependee.ub.tree.replaceParameters(paramTrees).simplify } else { StringTreeNode.ub } @@ -109,12 +105,11 @@ object ContextStringAnalysisState { } private[string] case class MethodParameterContextStringAnalysisState( - private val _entity: MethodParameterContext + entity: MethodParameterContext ) { - def entity: MethodParameterContext = _entity - def dm: DeclaredMethod = _entity.context.method - def index: Int = _entity.index + def dm: DeclaredMethod = entity.context.method + def index: Int = entity.index // Callers private[string] type CallerContext = (Context, Int) From 04d9271cc7f80e6fdd87485508ef0887d2778a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 23:28:12 +0200 Subject: [PATCH 561/583] Remove unused code --- .../string/flowanalysis/package.scala | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index 30f1a828d5..49135cc4b8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -8,10 +8,8 @@ package string import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG -import scalax.collection.OneOrMore import scalax.collection.edges.DiEdge import scalax.collection.generic.Edge -import scalax.collection.hyperedges.DiHyperEdge import scalax.collection.immutable.Graph import scalax.collection.immutable.TypedGraphFactory import scalax.collection.io.dot.DotAttr @@ -155,30 +153,5 @@ package object flowanalysis { iNodeTransformer = Some(nodeTransformer) ) } - - def enrichWithControlTree( - flowGraph: FlowGraph, - controlTree: ControlTree - ): Graph[FlowGraphNode, Edge[FlowGraphNode]] = { - var combinedGraph = flowGraph - .++[FlowGraphNode, DiEdge[FlowGraphNode]](controlTree.nodes.map(_.outer), Iterable.empty) - .asInstanceOf[Graph[FlowGraphNode, Edge[FlowGraphNode]]] - - for { - node <- controlTree.nodes.toOuter - nodes = combinedGraph.nodes.filter((n: Graph[FlowGraphNode, Edge[FlowGraphNode]]#NodeT) => - n.outer.nodeIds.subsetOf(node.nodeIds) - ).map(_.outer) - actualSubsetNodes = nodes.filter(n => n.nodeIds != node.nodeIds) - remainingNodes = actualSubsetNodes.filter(n => - !actualSubsetNodes.exists(nn => n.nodeIds != nn.nodeIds && n.nodeIds.subsetOf(nn.nodeIds)) - ) - if remainingNodes.size > 1 - } { - combinedGraph = combinedGraph.incl(DiHyperEdge(OneOrMore(node), OneOrMore.from(remainingNodes).get)) - } - - combinedGraph - } } } From 2489ed6618f6d9b9ddc0989db0b1bd5ea6044bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 11 Sep 2024 23:34:04 +0200 Subject: [PATCH 562/583] Simplify supported types and move into their only usage --- .../InterpretationHandler.scala | 41 ------------------- .../L3FieldReadInterpreter.scala | 13 +++++- 2 files changed, 12 insertions(+), 42 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index ec28f8bff8..86e0261f46 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -6,7 +6,6 @@ package analyses package string package interpretation -import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.string.StringTreeNode @@ -80,44 +79,4 @@ abstract class InterpretationHandler extends FPCFAnalysis with UniversalStringCo object InterpretationHandler { def getEntity(implicit state: InterpretationState): MethodPC = MethodPC(state.pc, state.dm) - - /** - * This function checks whether a given type is a supported primitive type. Supported currently - * means short, int, float, or double. - */ - private 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 - * `typeName` may be approximated by the string analysis better than just the lower bound. - * - * @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" || isSupportedPrimitiveNumberType(typeName) || - typeName == "java.lang.String" || typeName == "java.lang.String[]" - - /** - * Determines whether a given element is supported by the string analysis. - * - * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, - * see [[InterpretationHandler.isSupportedType(String)]]. - */ - def isSupportedType(v: V): Boolean = - if (v.value.isPrimitiveValue) { - isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) - } else { - try { - isSupportedType(v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava) - } catch { - case _: Exception => false - } - } - - def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index baac0be6b2..000f22ccb0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -10,6 +10,8 @@ package interpretation import scala.collection.mutable.ListBuffer import org.opalj.br.DeclaredField +import org.opalj.br.FieldType +import org.opalj.br.ObjectType import org.opalj.br.PUVar import org.opalj.br.analyses.DeclaredFields import org.opalj.br.analyses.SomeProject @@ -79,7 +81,7 @@ class L3FieldReadInterpreter( override def interpretExpr(target: PV, fieldRead: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - if (!InterpretationHandler.isSupportedType(fieldRead.declaredFieldType)) { + if (!L3FieldReadInterpreter.isSupportedType(fieldRead.declaredFieldType)) { return failure(target) } @@ -217,3 +219,12 @@ class L3FieldReadInterpreter( } } } + +object L3FieldReadInterpreter { + + /** + * Checks whether the given type is supported by the field read analysis, i.e. if it may contain values desirable + * AND resolvable by the string analysis as a whole. + */ + private def isSupportedType(fieldType: FieldType): Boolean = fieldType.isBaseType || fieldType == ObjectType.String +} From 63e6a0e35fd5b8ffd7ecf504e599b447646f7ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 12 Sep 2024 09:36:43 +0200 Subject: [PATCH 563/583] Fix formatting --- .../l1/interpretation/L1StaticFunctionCallInterpreter.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 99a071e2b3..9e37237740 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -38,9 +38,8 @@ case class L1StaticFunctionCallInterpreter()( state: InterpretationState ): ProperPropertyComputationResult = { call.name match { - case "getProperty" if call.declaringClass == ObjectType.System => - interpretGetSystemPropertiesCall(target) - case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) + case "getProperty" if call.declaringClass == ObjectType.System => interpretGetSystemPropertiesCall(target) + case "valueOf" if call.declaringClass == ObjectType.String => processStringValueOf(target, call) case _ if call.descriptor.returnType == ObjectType.String || call.descriptor.returnType == ObjectType.Object => From d431a929a2e26e811e1ec4bc5e1a501766355295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:02:31 +0200 Subject: [PATCH 564/583] Compute string analysis within callgraph in the tests --- .../scala/org/opalj/fpcf/PropertiesTest.scala | 29 +++++++++++---- .../org/opalj/fpcf/StringAnalysisTest.scala | 36 +++++++++++-------- .../SystemPropertiesAnalysis.scala | 28 --------------- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala index 5b0ebc2ca7..9ed2f1476d 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala @@ -365,21 +365,38 @@ abstract class PropertiesTest extends AnyFunSpec with Matchers { afterPhaseScheduling: (Project[URL], List[ComputationSpecification[FPCFAnalysis]]) => Unit = (_, _) => () ): TestContext = { try { - val p = FixtureProject.recreate { piKeyUnidueId => piKeyUnidueId != PropertyStoreKey.uniqueId } // to ensure that this project is not "polluted" - implicit val logContext: LogContext = p.logContext + val p = FixtureProject.recreate { piKeyUniqueId => piKeyUniqueId != PropertyStoreKey.uniqueId } // to ensure that this project is not "polluted" + init(p) + executeAnalysesForProject(p, analysisRunners, afterPhaseScheduling = afterPhaseScheduling(p, _)) + } catch { + case t: Throwable => + t.printStackTrace() + t.getSuppressed.foreach(e => e.printStackTrace()) + throw t; + } + } + + def executeAnalysesForProject( + project: Project[URL], + analysisRunners: Iterable[ComputationSpecification[FPCFAnalysis]], + afterPhaseScheduling: List[ComputationSpecification[FPCFAnalysis]] => Unit = _ => () + ): TestContext = { + try { + implicit val logContext: LogContext = project.logContext + PropertyStore.updateDebug(true) - p.getOrCreateProjectInformationKeyInitializationData( + project.getOrCreateProjectInformationKeyInitializationData( PropertyStoreKey, (context: List[PropertyStoreContext[AnyRef]]) => PKESequentialPropertyStore(context: _*) ) - val ps = p.get(PropertyStoreKey) + val ps = project.get(PropertyStoreKey) - val (_, csas) = p.get(FPCFAnalysesManagerKey).runAll(analysisRunners, afterPhaseScheduling(p, _)) - TestContext(p, ps, csas.collect { case (_, as) => as }) + val (_, csas) = project.get(FPCFAnalysesManagerKey).runAll(analysisRunners, afterPhaseScheduling(_)) + TestContext(project, ps, csas.collect { case (_, as) => as }) } catch { case t: Throwable => t.printStackTrace() 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 db1dd38975..1a405dec29 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 @@ -47,6 +47,7 @@ import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.cg.CallGraphKey import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.StringAnalysis @@ -57,13 +58,15 @@ import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringAnalysis import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis import org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringAnalysis -import org.opalj.tac.fpcf.analyses.systemproperties.EagerSystemPropertiesAnalysisScheduler +import org.opalj.tac.fpcf.analyses.systemproperties.TriggeredSystemPropertiesAnalysisScheduler sealed abstract class StringAnalysisTest extends PropertiesTest { // The name of the method from which to extract PUVars to analyze. val nameTestMethod: String = "analyzeString" + final val callGraphKey: CallGraphKey = RTACallGraphKey + def level: Level def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] def domainLevel: DomainLevel @@ -86,7 +89,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { override def fixtureProjectPackage: List[String] = List("org/opalj/fpcf/fixtures/string") - override final def init(p: Project[URL]): Unit = { + override def init(p: Project[URL]): Unit = { val domain = domainLevel match { case DomainLevel.L1 => classOf[DefaultDomainWithCFGAndDefUse[_]] case DomainLevel.L2 => classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] @@ -101,19 +104,20 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { case Some(_) => m => domain.getConstructors.head.newInstance(p, m).asInstanceOf[Domain with RecordDefUse] } - initBeforeCallGraph(p) - - p.get(RTACallGraphKey) + val typeIterator = callGraphKey.getTypeIterator(p) + p.updateProjectInformationKeyInitializationData(ContextProviderKey) { _ => typeIterator } } - def initBeforeCallGraph(p: Project[URL]): Unit = {} - describe(s"using level=$level, domainLevel=$domainLevel, soundness=$soundnessMode") { describe(s"the string analysis is started") { var entities = Iterable.empty[(VariableContext, Method)] - val as = executeAnalyses( - analyses, - (project, currentPhaseAnalyses) => { + val project = FixtureProject.recreate { piKeyUniqueId => piKeyUniqueId != PropertyStoreKey.uniqueId } + init(project) + + val as = executeAnalysesForProject( + project, + callGraphKey.allCallGraphAnalyses(project) ++ analyses, + currentPhaseAnalyses => { if (currentPhaseAnalyses.exists(_.derives.exists(_.pk == StringConstancyProperty))) { val ps = project.get(PropertyStoreKey) entities = determineEntitiesToAnalyze(project) @@ -125,7 +129,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { as.propertyStore.waitOnPhaseCompletion() as.propertyStore.shutdown() - validateProperties(as, determineEAS(entities, as.project), Set("StringConstancy")) + validateProperties(as, determineEAS(entities, project), Set("StringConstancy")) } } @@ -301,7 +305,7 @@ sealed abstract class L1StringAnalysisTest extends StringAnalysisTest { override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL1StringAnalysis.allRequiredAnalyses :+ - EagerSystemPropertiesAnalysisScheduler + TriggeredSystemPropertiesAnalysisScheduler } } @@ -335,7 +339,7 @@ sealed abstract class L2StringAnalysisTest extends StringAnalysisTest { override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL2StringAnalysis.allRequiredAnalyses :+ - EagerSystemPropertiesAnalysisScheduler + TriggeredSystemPropertiesAnalysisScheduler } } @@ -370,10 +374,12 @@ sealed abstract class L3StringAnalysisTest extends StringAnalysisTest { override final def analyses: Iterable[ComputationSpecification[FPCFAnalysis]] = { LazyL3StringAnalysis.allRequiredAnalyses :+ EagerFieldAccessInformationAnalysis :+ - EagerSystemPropertiesAnalysisScheduler + TriggeredSystemPropertiesAnalysisScheduler } - override def initBeforeCallGraph(p: Project[URL]): Unit = { + override def init(p: Project[URL]): Unit = { + super.init(p) + p.updateProjectInformationKeyInitializationData(FieldAccessInformationKey) { case None => Seq(EagerFieldAccessInformationAnalysis) case Some(requirements) => requirements :+ EagerFieldAccessInformationAnalysis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala index 96d3417eaf..95b83e0947 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala @@ -10,7 +10,6 @@ import org.opalj.br.ObjectType import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.properties.SystemProperties import org.opalj.br.fpcf.properties.cg.Callers @@ -152,30 +151,3 @@ object TriggeredSystemPropertiesAnalysisScheduler extends BasicFPCFTriggeredAnal override def derivesCollaboratively: Set[PropertyBounds] = PropertyBounds.ubs(SystemProperties) } - -object EagerSystemPropertiesAnalysisScheduler extends BasicFPCFEagerAnalysisScheduler { - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeIteratorKey) - - override def uses: Set[PropertyBounds] = PropertyBounds.ubs( - Callers, - TACAI, - StringConstancyProperty, - SystemProperties - ) - - override def start( - p: SomeProject, - ps: PropertyStore, - unused: Null - ): SystemPropertiesAnalysis = { - val analysis = new SystemPropertiesAnalysis(p) - val dm = p.get(DeclaredMethodsKey) - ps.scheduleEagerComputationsForEntities(p.allMethods.map(dm.apply))(analysis.analyze) - analysis - } - - override def derivesEagerly: Set[PropertyBounds] = Set.empty - - override def derivesCollaboratively: Set[PropertyBounds] = PropertyBounds.ubs(SystemProperties) -} From 1b1f0bac8ae962ea23716a7f44905676213d98da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:05:26 +0200 Subject: [PATCH 565/583] Add invalid string constancy level --- .../properties/string/StringConstancyLevel.scala | 13 +++++++++++-- .../br/fpcf/properties/string/StringTreeNode.scala | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index a9acbdd949..63c0a2fcf4 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -12,6 +12,11 @@ sealed trait StringConstancyLevel object StringConstancyLevel { + /** + * Indicates that a string has no value at a given read operation. + */ + case object Invalid extends StringConstancyLevel + /** * Indicates that a string has a constant value at a given read operation. */ @@ -43,8 +48,10 @@ object StringConstancyLevel { Dynamic } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { PartiallyConstant - } else { + } else if (level1 == Constant || level2 == Constant) { Constant + } else { + Invalid } } @@ -65,7 +72,9 @@ object StringConstancyLevel { level1: StringConstancyLevel, level2: StringConstancyLevel ): StringConstancyLevel = { - if (level1 == PartiallyConstant || level2 == PartiallyConstant) { + if (level1 == Invalid || level2 == Invalid) { + PartiallyConstant + } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { PartiallyConstant } else if ((level1 == Constant && level2 == Dynamic) || (level1 == Dynamic && level2 == Constant)) { PartiallyConstant diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 9db03ddfa5..930a29814a 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -371,7 +371,7 @@ object StringTreeParameter { object StringTreeInvalidElement extends SimpleStringTreeNode { override def _toRegex: String = throw new UnsupportedOperationException() - override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant + override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Invalid override def isInvalid: Boolean = true } From 9e991f9897fd9daa758abb98f468565950dfa273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:08:54 +0200 Subject: [PATCH 566/583] Fix return values of many string analyis parts --- .../opalj/fpcf/fixtures/string/Complex.java | 32 +++------------- .../opalj/fpcf/fixtures/string/External.java | 36 ++++++++++++++---- .../fpcf/fixtures/string/FunctionCalls.java | 9 +++-- .../string/SimpleStringBuilderOps.java | 12 ++++-- .../fpcf/fixtures/string/SimpleStringOps.java | 11 +++--- .../string/StringConstancyProperty.scala | 10 ++++- .../fpcf/analyses/string/StringAnalysis.scala | 11 ++++-- .../analyses/string/StringAnalysisState.scala | 38 +++++++++---------- .../analyses/string/StringInterpreter.scala | 18 +++++---- .../MethodStringFlowAnalysis.scala | 9 ++++- .../MethodStringFlowAnalysisState.scala | 18 ++++----- .../InterpretationHandler.scala | 6 +-- .../L1FunctionCallInterpreter.scala | 3 +- .../L1VirtualFunctionCallInterpreter.scala | 12 +++--- .../L1VirtualMethodCallInterpreter.scala | 4 +- .../L3FieldReadInterpreter.scala | 14 +------ .../trivial/TrivialStringAnalysis.scala | 11 ++---- .../string/StringFlowFunction.scala | 6 +-- 18 files changed, 135 insertions(+), 125 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java index 0a12b8e1e1..6f08032833 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java @@ -16,38 +16,16 @@ public class Complex { */ public void analyzeString(String s) {} - /** - * Extracted from com.oracle.webservices.internal.api.message.BasePropertySet, has two def-sites and one use-site - */ - @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "(s.*|set.*)") - @Failure(n = 0, levels = Level.L0) - 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"); - } - } - /** * Taken from com.sun.javafx.property.PropertyReference#reflect. */ @Failure(n = 0, levels = Level.L0) - @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, World.*)") - @PartiallyConstant(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(get.*|getHello, Worldjava.lang.Runtime)") + @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get-.*|get-Hello, World-.*)") + @PartiallyConstant(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(get-.*|get-Hello, World-java.lang.Runtime)") public void complexDependencyResolve(String s, Class clazz) { - String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : - getHelloWorld() + getRuntimeClassName(); - String getterName = "get" + properName; + String properName = s.length() == 1 ? s.substring(0, 1) : + getHelloWorld() + "-" + getRuntimeClassName(); + String getterName = "get-" + properName; Method m; try { m = clazz.getMethod(getterName); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java index e67e87e856..e3f8dced78 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java @@ -6,6 +6,7 @@ import javax.management.remote.rmi.RMIServer; import java.io.File; import java.io.FileNotFoundException; +import java.util.Random; import java.util.Scanner; /** @@ -17,13 +18,21 @@ public class External { public static String nonFinalStaticField = "will not be revealed here"; public static final String finalStaticField = "mine"; private String fieldWithSelfInit = "init field value"; - private static final String fieldWithSelfInitWithOutOfScopeCall = RMIServer.class.getName() + "Impl_Stub"; + private static final String fieldWithSelfInitWithComplexInit; private String fieldWithConstructorInit; private float fieldWithConstructorParameterInit; private String writeInSameMethodField; private String noWriteField; private Object unsupportedTypeField; + static { + if (new Random().nextBoolean()) { + fieldWithSelfInitWithComplexInit = "Impl_Stub_1"; + } else { + fieldWithSelfInitWithComplexInit = "Impl_Stub_2"; + } + } + public External(float e) { fieldWithConstructorInit = "initialized by constructor"; fieldWithConstructorParameterInit = e; @@ -61,10 +70,10 @@ public void fieldWithInitRead() { analyzeString(fieldWithSelfInit.toString()); } - @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*Impl_Stub") + @Constant(n = 0, levels = Level.TRUTH, value = "(Impl_Stub_1|Impl_Stub_2)") @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2 }) public void fieldWithInitWithOutOfScopeRead() { - analyzeString(fieldWithSelfInitWithOutOfScopeCall); + analyzeString(fieldWithSelfInitWithComplexInit); } @Constant(n = 0, levels = Level.TRUTH, value = "initialized by constructor") @@ -106,12 +115,24 @@ public void nonSupportedFieldTypeRead() { analyzeString(unsupportedTypeField.toString()); } - @Dynamic(n = 0, levels = Level.TRUTH, value = ".*") - @Dynamic(n = 1, levels = Level.TRUTH, value = ".*") + public void parameterCaller() { + this.parameterRead("some-param-value", new StringBuilder("some-other-param-value")); + } + + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-param-value") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-param-value)", + reason = "method is an entry point and thus has callers with unknown context") + @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-other-param-value") + @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-other-param-value)", + reason = "method is an entry point and thus has callers with unknown context") @Failure(n = 1, levels = Level.L0) - @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "value=.*") + @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)", + reason = "method is an entry point and thus has callers with unknown context") @Failure(n = 2, levels = Level.L0) - @PartiallyConstant(n = 3, levels = Level.TRUTH, value = "value=.*.*") + @Constant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value-some-other-param-value") + @PartiallyConstant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)-(.*|some-other-param-value)", + reason = "method is an entry point and thus has callers with unknown context") @Failure(n = 3, levels = Level.L0) public void parameterRead(String stringValue, StringBuilder sbValue) { analyzeString(stringValue); @@ -122,6 +143,7 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { sb.append(stringValue); analyzeString(sb.toString()); + sb.append("-"); sb.append(sbValue.toString()); analyzeString(sb.toString()); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java index 31899924d1..7a49dbe552 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java @@ -161,7 +161,8 @@ private static String severalReturnValuesWithIfElseFunction(int i) { } } - @Constant(n = 0, levels = Level.TRUTH, value = "(Hello, World|my.helper.Class)") + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "(Hello, World|my.helper.Class)") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|Hello, World|my.helper.Class)") @Failure(n = 0, levels = Level.L0) public String calleeWithFunctionParameter(String s, float i) { analyzeString(s); @@ -179,7 +180,8 @@ public void secondCallerForCalleeWithFunctionParameter() { calleeWithFunctionParameter(getHelperClassProxy(), 900); } - @Constant(n = 0, levels = Level.TRUTH, value = "(Hello, World|my.helper.Class)") + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "(Hello, World|my.helper.Class)") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|Hello, World|my.helper.Class)") @Failure(n = 0, levels = Level.L0) public String calleeWithFunctionParameterMultipleCallsInSameMethodTest(String s, float i) { analyzeString(s); @@ -191,7 +193,8 @@ public void callerForCalleeWithFunctionParameterMultipleCallsInSameMethodTest() calleeWithFunctionParameterMultipleCallsInSameMethodTest(getHelperClassProxy(), 900); } - @Constant(n = 0, levels = Level.TRUTH, value = "(string.1|string.2)") + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "(string.1|string.2)") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|string.1|string.2)") public String calleeWithStringParameterMultipleCallsInSameMethodTest(String s, float i) { analyzeString(s); return s; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java index 48abb61329..b2b7317c14 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java @@ -82,13 +82,17 @@ public void advancedClearExampleWithSetLength(int value) { analyzeString(sb.toString()); } - @Dynamic(n = 0, levels = Level.TRUTH, value = ".*") - @Failure(n = 0, levels = Level.L0) - @PartiallyConstant(n = 1, levels = Level.TRUTH, value = "(.*Goodbye|init_value:Hello, world!Goodbye)") + @Constant(n = 0, levels = Level.TRUTH, value = "replaced_value") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }) + @Constant(n = 1, levels = Level.TRUTH, value = "(...:Goodbye|init_value:Hello, world!Goodbye)") @Failure(n = 1, levels = Level.L0) + @Constant(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.LOW, + value = "init_value:Hello, world!Goodbye") + @PartiallyConstant(n = 1, levels = { Level.L1, Level.L2, Level.L3 }, soundness = SoundnessMode.HIGH, + value = "(.*Goodbye|init_value:Hello, world!Goodbye)") public void replaceExamples(int value) { StringBuilder sb1 = new StringBuilder("init_value"); - sb1.replace(0, 5, "replaced_value"); + sb1.replace(0, 5, "replaced_"); analyzeString(sb1.toString()); sb1 = new StringBuilder("init_value:"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java index 9a7544c5cb..0e9419e2e6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java @@ -115,16 +115,17 @@ public void appendWithTwoDefSites(int i) { @Constant(n = 0, levels = Level.TRUTH, value = "(Some|SomeOther)") @Constant(n = 0, levels = Level.L0, soundness = SoundnessMode.LOW, value = "Some") @Dynamic(n = 0, levels = Level.L0, soundness = SoundnessMode.HIGH, value = "(.*|Some)") - @Dynamic(n = 1, levels = Level.TRUTH, value = "(.*|Some)") - @PartiallyConstant(n = 2, levels = Level.TRUTH, value = "(Some.*|SomeOther)") + @Constant(n = 1, levels = Level.TRUTH, value = "(Impostor|Some)") + @Constant(n = 2, levels = Level.TRUTH, value = "(SomeImpostor|SomeOther)") @Failure(n = 2, levels = Level.L0) - public void ternaryOperators(boolean flag, String param) { + public void ternaryOperators(boolean flag) { String s1 = "Some"; String s2 = s1 + "Other"; + String s3 = "Impostor"; analyzeString(flag ? s1 : s2); - analyzeString(flag ? s1 : param); - analyzeString(flag ? s1 + param : s2); + analyzeString(flag ? s1 : s3); + analyzeString(flag ? s1 + s3 : s2); } @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index 35860b1d3a..cffdf7c7ac 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -8,6 +8,7 @@ package string import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyIsNotDerivedByPreviouslyExecutedAnalysis import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore @@ -46,7 +47,14 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta final val key: PropertyKey[StringConstancyProperty] = { PropertyKey.create( PropertyKeyName, - (_: PropertyStore, _: FallbackReason, _: Entity) => lb + (_: PropertyStore, reason: FallbackReason, _: Entity) => { + reason match { + case PropertyIsNotDerivedByPreviouslyExecutedAnalysis => + lb + case _ => + throw new IllegalStateException(s"Analysis required for property: $PropertyKeyName") + } + } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index b331d41731..b6fa15b1be 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -77,15 +77,20 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec implicit val state: ContextFreeStringAnalysisState = ContextFreeStringAnalysisState(vd, ps(vd.m, TACAI.key)) if (state.tacaiDependee.isRefinable) { - InterimResult.forUB( + InterimResult( state.entity, + StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees, continuation(state) ) } else if (state.tacaiDependee.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - Result(state.entity, StringConstancyProperty.lb) + Result( + state.entity, + if (soundnessMode.isHigh) StringConstancyProperty.lb + else StringConstancyProperty.ub + ) } else { computeResults } @@ -361,7 +366,7 @@ private[string] class MethodParameterContextStringAnalysis(override val project: continuation(state) ) } else { - Result(state.entity, StringConstancyProperty(state.finalTree)) + Result(state.entity, StringConstancyProperty(state.currentTreeUB)) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index c26dfece74..01dd7a454d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -172,29 +172,25 @@ private[string] case class MethodParameterContextStringAnalysisState( def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def currentTreeUB: StringTreeNode = { - if (_discoveredUnknownTAC) { - StringTreeNode.lb - } else { - val paramOptions = _methodToEntityMapping.keys.toSeq - .sortBy(_.id) - .flatMap { dm => - _methodToEntityMapping(dm) - .map(_paramDependees) - .filter(_.hasUBP) - .map(_.ub.tree) - } - - StringTreeOr(paramOptions).simplify + def currentTreeUB(implicit soundnessMode: SoundnessMode): StringTreeNode = { + var paramOptions = _methodToEntityMapping.keys.toSeq + .sortBy(_.id) + .flatMap { dm => + _methodToEntityMapping(dm) + .map(_paramDependees) + .filter(_.hasUBP) + .map(_.ub.tree) + } + + if (soundnessMode.isHigh && ( + _discoveredUnknownTAC || + _callersDependee.exists(cd => cd.hasUBP && cd.ub.hasCallersWithUnknownContext) + ) + ) { + paramOptions :+= StringTreeNode.lb } - } - def finalTree: StringTreeNode = { - if (_methodToEntityMapping.isEmpty) { - StringTreeNode.lb - } else { - currentTreeUB - } + StringTreeOr(paramOptions).simplify } def hasDependees: Boolean = _callersDependee.exists(_.isRefinable) || diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 5066af4b26..d2b7cd6638 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -5,6 +5,7 @@ package fpcf package analyses package string +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -27,6 +28,9 @@ trait StringInterpreter { */ def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult + def failureTree(implicit soundnessMode: SoundnessMode): StringTreeNode = + StringInterpreter.failureTree + def failure(v: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = StringInterpreter.failure(v) @@ -42,16 +46,16 @@ trait StringInterpreter { object StringInterpreter { + def failureTree(implicit soundnessMode: SoundnessMode): StringTreeNode = { + if (soundnessMode.isHigh) StringTreeNode.lb + else StringTreeNode.ub + } + def failure(v: V)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = failure(v.toPersistentForm(state.tac.stmts)) - def failure(pv: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = { - if (soundnessMode.isHigh) { - computeFinalResult(StringFlowFunctionProperty.lb(state.pc, pv)) - } else { - computeFinalResult(StringFlowFunctionProperty.ub(state.pc, pv)) - } - } + def failure(pv: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, pv, failureTree)) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(web, sff))) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index 17181cebd0..bbd9835b2e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -63,15 +63,20 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn if (excludedPackages.exists(method.classFile.thisType.packageName.startsWith(_))) { Result(state.entity, MethodStringFlow.lb) } else if (state.tacDependee.isRefinable) { - InterimResult.forUB( + InterimResult( state.entity, + MethodStringFlow.lb, MethodStringFlow.ub, Set(state.tacDependee), continuation(state) ) } else if (state.tacDependee.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - Result(state.entity, MethodStringFlow.lb) + Result( + state.entity, + if (soundnessMode.isHigh) MethodStringFlow.lb + else MethodStringFlow.ub + ) } else { determinePossibleStrings(state) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala index f0b15b541a..f4ba408126 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala @@ -11,7 +11,6 @@ import scala.collection.mutable import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.br.DefinedMethod import org.opalj.br.Method -import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement import org.opalj.br.fpcf.properties.string.StringTreeParameter import org.opalj.collection.immutable.IntTrieSet @@ -73,7 +72,7 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var } private var _startEnv: StringTreeEnvironment = StringTreeEnvironment(Map.empty) - def getStartEnvAndReset: StringTreeEnvironment = { + def getStartEnvAndReset(implicit soundnessMode: SoundnessMode): StringTreeEnvironment = { if (pcToWebChangeMapping.exists(_._2)) { val webs = getWebs val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] @@ -103,16 +102,15 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var val startMap = indexedWebs.filter(_ != null) .map { web: PDUWeb => - val defPCs = web.defPCs.toList.sorted - if (defPCs.head >= 0) { + val defPC = web.defPCs.toList.min + + if (defPC >= 0) { (web, StringTreeInvalidElement) + } else if (defPC < -1 && defPC > ImmediateVMExceptionsOriginOffset) { + (web, StringTreeParameter.forParameterPC(defPC)) } else { - val pc = defPCs.head - if (pc == -1 || pc <= ImmediateVMExceptionsOriginOffset) { - (web, StringTreeDynamicString) - } else { - (web, StringTreeParameter.forParameterPC(pc)) - } + // IMPROVE interpret "this" (parameter pc -1) with reference to String and StringBuilder classes + (web, StringInterpreter.failureTree) } }.toMap _startEnv = StringTreeEnvironment(startMap) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 86e0261f46..7c1805e62e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -8,7 +8,6 @@ package interpretation import org.opalj.br.Method import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -32,15 +31,16 @@ abstract class InterpretationHandler extends FPCFAnalysis with UniversalStringCo implicit val state: InterpretationState = InterpretationState(entity.pc, entity.dm, tacaiEOptP) if (tacaiEOptP.isRefinable) { - InterimResult.forUB( + InterimResult( InterpretationHandler.getEntity, + StringFlowFunctionProperty.lb, StringFlowFunctionProperty.ub, Set(state.tacDependee), continuation(state) ) } else if (tacaiEOptP.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - StringInterpreter.computeFinalResult(StringFlowFunctionProperty.constForAll(StringTreeNode.lb)) + StringInterpreter.computeFinalResult(StringFlowFunctionProperty.constForAll(StringInterpreter.failureTree)) } else { processStatementForState } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 8d586b6435..44e77b11c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -35,6 +35,7 @@ trait L1FunctionCallInterpreter override type E <: FunctionCall[V] implicit val ps: PropertyStore + implicit val soundnessMode: SoundnessMode type CallState <: FunctionCallState @@ -119,7 +120,7 @@ trait L1FunctionCallInterpreter StringTreeOr { callState.calleeMethods.map { m => if (callState.hasUnresolvableReturnValue(m)) { - StringTreeNode.lb + failureTree } else if (callState.returnDependees.contains(m)) { StringTreeOr(callState.returnDependees(m).map { rd => if (rd.hasUBP) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 97413f6a3c..65baf3fc6d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -96,7 +96,7 @@ class L1VirtualFunctionCallInterpreter( */ private def interpretReplaceCall(target: PV)(implicit state: InterpretationState): ProperPropertyComputationResult = { // Improve: Support fluent API by returning combined web for both assignment target and call target - computeFinalResult(StringFlowFunctionProperty.lb(state.pc, target)) + failure(target) } } @@ -163,6 +163,8 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri override type E <: VirtualFunctionCall[V] + implicit val soundnessMode: SoundnessMode + def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { @@ -182,12 +184,12 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri case StringTreeConst(string) if intVal <= string.length => env.update(state.pc, at.get, StringTreeConst(string.substring(intVal))) case _ => - env.update(state.pc, at.get, StringTreeNode.lb) + env.update(state.pc, at.get, failureTree) } } ) case _ => - computeFinalResult(StringFlowFunctionProperty.ub(state.pc, at.get)) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, at.get, failureTree)) } case 2 => @@ -207,12 +209,12 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri StringTreeConst(string.substring(firstIntVal, secondIntVal)) ) case _ => - env.update(state.pc, at.get, StringTreeNode.lb) + env.update(state.pc, at.get, failureTree) } } ) case _ => - computeFinalResult(StringFlowFunctionProperty.ub(state.pc, at.get)) + computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, at.get, failureTree)) } case _ => throw new IllegalStateException( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index f12261bd3c..30c3ba2d75 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -9,7 +9,6 @@ package interpretation import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.br.fpcf.properties.string.StringTreeEmptyConst -import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -51,8 +50,7 @@ case class L1VirtualMethodCallInterpreter()( env.update( state.pc, pReceiver, - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub + failureTree ) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 000f22ccb0..e4d2ba2cde 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -111,11 +111,7 @@ class L3FieldReadInterpreter( return computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, accessState.target, - StringTreeOr.fromNodes( - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub, - StringTreeNull - ) + StringTreeOr.fromNodes(failureTree, StringTreeNull) )) } @@ -166,13 +162,7 @@ class L3FieldReadInterpreter( if (tree.parameterIndices.nonEmpty) { // We cannot handle write values that contain parameter indices since resolving the parameters // requires context and this interpreter is present in multiple contexts. - tree.replaceParameters(tree.parameterIndices.map { paramIndex => - ( - paramIndex, - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub - ) - }.toMap) + tree.replaceParameters(tree.parameterIndices.map((_, failureTree)).toMap) } else tree } else StringTreeNode.ub diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index 08f6d9ef74..b3ef60a661 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -59,7 +59,7 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly ) } else if (state.tacDependee.ub.tac.isEmpty) { // No TAC available, e.g., because the method has no body - Result(state.entity, StringConstancyProperty(failure)) + Result(state.entity, StringConstancyProperty(StringInterpreter.failureTree)) } else { determinePossibleStrings } @@ -83,11 +83,11 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly def mapDefPCToStringTree(defPC: Int): StringTreeNode = { if (defPC < 0) { - failure + StringInterpreter.failureTree } else { tac.stmts(valueOriginOfPC(defPC, tac.pcToIndex).get).asAssignment.expr match { case StringConst(_, v) => StringTreeConst(v) - case _ => failure + case _ => StringInterpreter.failureTree } } } @@ -102,11 +102,6 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly Result(state.entity, StringConstancyProperty(tree)) } - - private def failure: StringTreeNode = { - if (soundnessMode.isHigh) StringTreeNode.lb - else StringTreeNode.ub - } } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 77f19bf118..753efc62d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -5,7 +5,6 @@ package fpcf package properties package string -import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey @@ -40,7 +39,8 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat def apply(pc: Int, pv: PV, flow: StringFlowFunction): StringFlowFunctionProperty = StringFlowFunctionProperty(PDUWeb(pc, pv), flow) - def ub: StringFlowFunctionProperty = constForAll(StringTreeInvalidElement) + def lb: StringFlowFunctionProperty = constForAll(StringTreeNode.lb) + def ub: StringFlowFunctionProperty = constForAll(StringTreeNode.ub) def identity: StringFlowFunctionProperty = StringFlowFunctionProperty(Set.empty[PDUWeb], (env: StringTreeEnvironment) => env) @@ -53,7 +53,7 @@ object StringFlowFunctionProperty extends StringFlowFunctionPropertyMetaInformat constForVariableAt(pc, v, StringTreeNode.lb) def ub(pc: Int, v: PV): StringFlowFunctionProperty = - constForVariableAt(pc, v, StringTreeInvalidElement) + constForVariableAt(pc, v, StringTreeNode.ub) def constForVariableAt(pc: Int, v: PV, result: StringTreeNode): StringFlowFunctionProperty = StringFlowFunctionProperty(pc, v, (env: StringTreeEnvironment) => env.update(pc, v, result)) From 63511519218f3f861583f269a436eeb8853ad36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:09:15 +0200 Subject: [PATCH 567/583] Fix crash when no callers dependee was computed before but receives an update --- .../org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index b6fa15b1be..6ed43fd013 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -276,7 +276,7 @@ private[string] class MethodParameterContextStringAnalysis(override val project: implicit val _state: MethodParameterContextStringAnalysisState = state eps match { case UBP(callers: Callers) => - val oldCallers = state._callersDependee.get.ub + val oldCallers = if (state._callersDependee.get.hasUBP) state._callersDependee.get.ub else NoCallers state.updateCallers(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) handleNewCallers(oldCallers, callers) computeResults From 4ce89575d82a7f144faff03a13523ee520591ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:33:01 +0200 Subject: [PATCH 568/583] Simplify to string on constancy property --- .../br/fpcf/properties/string/StringConstancyProperty.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index cffdf7c7ac..91a0b787bb 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -24,12 +24,12 @@ class StringConstancyProperty( final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - val level = tree.constancyLevel.toString.toLowerCase - val strings = if (tree.simplify.isInvalid) { + val level = tree.constancyLevel + val strings = if (level == StringConstancyLevel.Invalid) { "No possible strings - Invalid Flow" } else tree.sorted.toRegex - s"Level: $level, Possible Strings: $strings" + s"Level: ${level.toString.toLowerCase}, Possible Strings: $strings" } override def hashCode(): Int = tree.hashCode() From c407442a4ce07fbcdbdf7032e6ceade310d4aaed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:33:20 +0200 Subject: [PATCH 569/583] Greatly refactor reflective call analysis tool --- .../info/StringAnalysisReflectiveCalls.scala | 286 +++++++++--------- 1 file changed, 138 insertions(+), 148 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 60d3fdf7f0..fd65f53ef0 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 @@ -6,10 +6,10 @@ package info import scala.annotation.switch import java.net.URL -import scala.collection.mutable import scala.collection.mutable.ListBuffer +import scala.util.Try -import org.opalj.br.Method +import org.opalj.br.DeclaredMethod import org.opalj.br.ObjectType import org.opalj.br.ReferenceType import org.opalj.br.analyses.BasicReport @@ -21,23 +21,18 @@ import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysesManagerKey import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.analyses.ContextProvider import org.opalj.br.fpcf.properties.string.StringConstancyLevel import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.br.instructions.Instruction import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.instructions.INVOKEVIRTUAL -import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimEUBP -import org.opalj.fpcf.InterimLUBP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEPS import org.opalj.tac.Assignment import org.opalj.tac.Call +import org.opalj.tac.ComputeTACAIKey import org.opalj.tac.ExprStmt import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt @@ -46,38 +41,73 @@ import org.opalj.tac.TACode import org.opalj.tac.V import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis +import org.opalj.tac.fpcf.analyses.fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysisScheduler +import org.opalj.tac.fpcf.analyses.string.LazyMethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.LazyStringAnalysis import org.opalj.tac.fpcf.analyses.string.VariableContext -import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis -import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis -import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.trivial.LazyTrivialStringAnalysis +import org.opalj.tac.fpcf.analyses.systemproperties.TriggeredSystemPropertiesAnalysisScheduler import org.opalj.util.PerformanceEvaluation.time /** - * 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[])`
    • - *
    + * 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. Includes an option to also analyze relevant JavaX Crypto API calls. * * @author Maximilian Rüsch */ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { + /** + * @param detectedValues 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 case class State(detectedValues: ListBuffer[(VariableContext, String)]) + private case class Configuration( - includeCrypto: Boolean, - private val runL0Analysis: Boolean + includeCrypto: Boolean, + private val analysisConfig: Configuration.AnalysisConfig ) { def analyses: Seq[FPCFLazyAnalysisScheduler] = { - if (runL0Analysis) LazyL0StringAnalysis.allRequiredAnalyses - else LazyL2StringAnalysis.allRequiredAnalyses + analysisConfig match { + case Configuration.TrivialAnalysis => Seq(LazyTrivialStringAnalysis) + case Configuration.LevelAnalysis(level) => + Seq( + LazyStringAnalysis, + LazyMethodStringFlowAnalysis, + Configuration.LevelToSchedulerMapping(level) + ) + } + } + } + + private object Configuration { + + private[Configuration] trait AnalysisConfig + private[Configuration] case object TrivialAnalysis extends AnalysisConfig + private[Configuration] case class LevelAnalysis(level: Int) extends AnalysisConfig + + final val LevelToSchedulerMapping = Map( + 0 -> LazyL0StringFlowAnalysis, + 1 -> LazyL1StringFlowAnalysis, + 2 -> LazyL2StringFlowAnalysis, + 3 -> LazyL3StringFlowAnalysis + ) + + def apply(parameters: Seq[String]): Configuration = { + val includeCrypto = parameters.contains("-includeCryptoApi") + val levelParameter = parameters.find(_.startsWith("-level=")).getOrElse("-level=trivial") + val analysisConfig = levelParameter.replace("-level=", "") match { + case "trivial" => TrivialAnalysis + case string => LevelAnalysis(string.toInt) + } + + new Configuration(includeCrypto, analysisConfig) } } @@ -107,24 +137,37 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { "java.lang.Class#getDeclaredMethod" ) - /** - * 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 entityContext = ListBuffer[(VariableContext, String)]() - - /** - * 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() - 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 analysisSpecificParametersDescription: String = + s""" + | [-includeCryptoApi] + | [-level=trivial|${Configuration.LevelToSchedulerMapping.keys.toSeq.sorted.mkString("|")}] + |""".stripMargin + + override def checkAnalysisSpecificParameters(parameters: Seq[String]): Iterable[String] = { + parameters.flatMap { + case "-includeCryptoApi" => None + case levelParameter if levelParameter.startsWith("-level=") => + levelParameter.replace("-level=", "") match { + case "trivial" => + None + case string + if Try(string.toInt).isSuccess + && Configuration.LevelToSchedulerMapping.keySet.contains(string.toInt) => + None + case value => + Some(s"Unknown level parameter value: $value") + } + + case param => Some(s"unknown parameter: $param") + } + } + /** * Retrieves all relevant method names, i.e., those methods from the Reflection API that have at least one string * argument and shall be considered by this analysis. The string are supposed to have the format as produced @@ -178,32 +221,35 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { * `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(pc: Int, method: Method, call: Call[V])( + private def processFunctionCall(pc: Int, dm: DeclaredMethod, call: Call[V])( implicit stmts: Array[Stmt[V]], ps: PropertyStore, contextProvider: ContextProvider, - declaredMethods: DeclaredMethods, + state: State, configuration: Configuration ): Unit = { if (isRelevantCall(call.declaringClass, call.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 == ObjectType.String => - val context = contextProvider.newContext(declaredMethods(method)) - val e = VariableContext(pc, call.params(index).asVar.toPersistentForm, context) + val e = VariableContext( + pc, + call.params(index).asVar.toPersistentForm, + contextProvider.newContext(dm) + ) ps.force(e, StringConstancyProperty.key) - entityContext.append((e, buildFQMethodName(call.declaringClass, call.name))) + state.detectedValues.append((e, buildFQMethodName(call.declaringClass, call.name))) case _ => } } } - private def processStatements(tac: TACode[TACMethodParameter, V], m: Method)( + private def processStatements(tac: TACode[TACMethodParameter, V], dm: DeclaredMethod)( implicit contextProvider: ContextProvider, ps: PropertyStore, - declaredMethods: DeclaredMethods, + state: State, configuration: Configuration ): Unit = { implicit val stmts: Array[Stmt[V]] = tac.stmts @@ -212,16 +258,16 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { (stmt.astID: @switch) match { case Assignment.ASTID => stmt match { case Assignment(pc, _, c: StaticFunctionCall[V]) => - processFunctionCall(pc, m, c) + processFunctionCall(pc, dm, c) case Assignment(pc, _, c: VirtualFunctionCall[V]) => - processFunctionCall(pc, m, c) + processFunctionCall(pc, dm, c) case _ => } case ExprStmt.ASTID => stmt match { case ExprStmt(pc, c: StaticFunctionCall[V]) => - processFunctionCall(pc, m, c) + processFunctionCall(pc, dm, c) case ExprStmt(pc, c: VirtualFunctionCall[V]) => - processFunctionCall(pc, m, c) + processFunctionCall(pc, dm, c) case _ => } case _ => @@ -229,118 +275,62 @@ object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { } } - private def continuation(m: Method)(eps: SomeEPS)( - implicit - contextProvider: ContextProvider, - declaredMethods: DeclaredMethods, - ps: PropertyStore, - configuration: Configuration - ): ProperPropertyComputationResult = { - eps match { - case FinalP(tac: TACAI) => - processStatements(tac.tac.get, m) - Result(m, tac) - case InterimLUBP(lb, ub) => - InterimResult( - m, - lb, - ub, - Set(eps), - continuation(m) - ) - case _ => throw new IllegalStateException("should never happen!") - } - } - - override def checkAnalysisSpecificParameters(parameters: Seq[String]): Seq[String] = { - parameters.flatMap { p => - p.toLowerCase match { - case "-includecryptoapi" => Nil - case "-l0" => Nil - case _ => List(s"Unknown parameter: $p") - } - } - } - override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean ): ReportableAnalysisResult = { - implicit val configuration: Configuration = Configuration( - // Check whether string-consuming methods of the javax.crypto API should be considered. Default is false. - includeCrypto = parameters.exists(p => p.equalsIgnoreCase("-includeCryptoApi")), - // Check whether the L0 analysis should be run. By default, L1 is selected. - runL0Analysis = parameters.exists(p => p.equalsIgnoreCase("-l0")) - ) + implicit val state: State = State(detectedValues = ListBuffer.empty) + implicit val configuration: Configuration = Configuration(parameters) + + val cgKey = RTACallGraphKey + val typeIterator = cgKey.getTypeIterator(project) + project.updateProjectInformationKeyInitializationData(ContextProviderKey) { _ => typeIterator } val manager = project.get(FPCFAnalysesManagerKey) - project.get(RTACallGraphKey) + val computeTac = project.get(ComputeTACAIKey) + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) - implicit val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - implicit val (propertyStore, _) = manager.runAll(configuration.analyses) - - // Stores the obtained results for each supported reflective operation - val resultMap = mutable.Map[String, ListBuffer[StringTreeNode]]() - relevantMethodNames.foreach { resultMap(_) = ListBuffer() } - - project.allMethodsWithBody.foreach { m => - val fqnMethodName = buildFQMethodName(m.classFile.thisType, m.name) - // To dramatically reduce work, quickly check if a method is ignored or not relevant at all - if (!ignoreMethods.contains(fqnMethodName) && instructionsContainRelevantMethod(m.body.get.instructions)) { - 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 { - processStatements(tacaiEOptP.ub.tac.get, m) - } - } else { - InterimResult( - m, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - Set(tacaiEOptP), - continuation(m) - ) - } - } - } time { - propertyStore.waitOnPhaseCompletion() - entityContext.foreach { - case (e, callName) => - propertyStore.properties(e).toIndexedSeq.foreach { - case FinalP(p: StringConstancyProperty) => - resultMap(callName).append(p.tree) - case InterimEUBP(_, ub: StringConstancyProperty) => - resultMap(callName).append(ub.tree) - case _ => - println(s"No result for $e in $callName found!") + manager.runAll( + cgKey.allCallGraphAnalyses(project) ++ + configuration.analyses ++ + Seq( + EagerFieldAccessInformationAnalysis, + ReflectionRelatedFieldAccessesAnalysisScheduler, + TriggeredSystemPropertiesAnalysisScheduler + ), + afterPhaseScheduling = _ => { + project.allMethodsWithBody.foreach { m => + // To dramatically reduce work, quickly check if a method is relevant at all + if (instructionsContainRelevantMethod(m.body.get.instructions)) { + processStatements(computeTac(m), declaredMethods(m)) + } } - } - } { t => println(s"Elapsed Time: ${t.toMilliseconds} ms") } + } + ) + } { t => println(s"Elapsed Time: ${t.toMilliseconds}") } - resultMapToReport(resultMap) - } + val resultMap = Map.from(relevantMethodNames.map((_, ListBuffer.empty[FinalEP[_, StringConstancyProperty]]))) + state.detectedValues.foreach { + case (e, callName) => + resultMap(callName).append(propertyStore(e, StringConstancyProperty.key).asFinal) + } - private def resultMapToReport(resultMap: mutable.Map[String, ListBuffer[StringTreeNode]]): 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.PartiallyConstant => partConstantCount += 1 - case StringConstancyLevel.Dynamic => dynamicCount += 1 - } - } + for ((reflectiveCall, stringTrees) <- resultMap.toSeq.sortBy(_._1)) { + val invalidCount = stringTrees.count(_.p.tree.constancyLevel == StringConstancyLevel.Invalid) + val constantCount = stringTrees.count(_.p.tree.constancyLevel == StringConstancyLevel.Constant) + val partiallyConstantCount = + stringTrees.count(_.p.tree.constancyLevel == StringConstancyLevel.PartiallyConstant) + val dynamicCount = stringTrees.count(_.p.tree.constancyLevel == StringConstancyLevel.Dynamic) - report.append(s"$reflectiveCall: ${entries.length}x") + report.append(s"$reflectiveCall: ${stringTrees.length}x") + report.append(s" -> Invalid: ${invalidCount}x") report.append(s" -> Constant: ${constantCount}x") - report.append(s" -> Partially Constant: ${partConstantCount}x") + report.append(s" -> Partially Constant: ${partiallyConstantCount}x") report.append(s" -> Dynamic: ${dynamicCount}x") } BasicReport(report) From 0a1e057d314c039d2446befcb83fc067ed6fc48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 16 Sep 2024 23:48:21 +0200 Subject: [PATCH 570/583] Simplify high soundness handling down to boolean type alias --- .../org/opalj/fpcf/StringAnalysisTest.scala | 2 +- .../fpcf/analyses/string/SoundnessMode.scala | 26 ------------------- .../fpcf/analyses/string/StringAnalysis.scala | 4 +-- .../analyses/string/StringAnalysisState.scala | 4 +-- .../analyses/string/StringInterpreter.scala | 12 ++++----- .../string/UniversalStringConfig.scala | 16 ++++++------ .../flowanalysis/DataFlowAnalysis.scala | 11 ++++---- .../MethodStringFlowAnalysis.scala | 4 +-- .../MethodStringFlowAnalysisState.scala | 2 +- .../BinaryExprInterpreter.scala | 6 ++--- .../L1FunctionCallInterpreter.scala | 2 +- .../L1NonVirtualFunctionCallInterpreter.scala | 3 +-- .../L1NonVirtualMethodCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 4 +-- .../L1VirtualFunctionCallInterpreter.scala | 15 +++++------ .../L1VirtualMethodCallInterpreter.scala | 2 +- .../L2VirtualFunctionCallInterpreter.scala | 6 ++--- .../L3FieldReadInterpreter.scala | 7 +++-- .../tac/fpcf/analyses/string/package.scala | 1 + 19 files changed, 48 insertions(+), 81 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala 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 1a405dec29..8fe203c910 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 @@ -79,7 +79,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } super.createConfig() - .withValue(UniversalStringConfig.SoundnessModeConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue(UniversalStringConfig.HighSoundnessConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) .withValue(StringAnalysis.DepthThresholdConfigKey, ConfigValueFactory.fromAnyRef(10)) .withValue( MethodStringFlowAnalysis.ExcludedPackagesConfigKey, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala deleted file mode 100644 index 2b8122c33a..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/SoundnessMode.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses -package string - -trait SoundnessMode { - - def isHigh: Boolean -} - -object SoundnessMode { - - def apply(high: Boolean): SoundnessMode = if (high) HighSoundness else LowSoundness -} - -object LowSoundness extends SoundnessMode { - - override def isHigh: Boolean = false -} - -object HighSoundness extends SoundnessMode { - - override def isHigh: Boolean = true -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 6ed43fd013..0e184f1847 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -88,7 +88,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec // No TAC available, e.g., because the method has no body Result( state.entity, - if (soundnessMode.isHigh) StringConstancyProperty.lb + if (highSoundness) StringConstancyProperty.lb else StringConstancyProperty.ub ) } else { @@ -173,7 +173,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec // String constancy information got too complex, abort. This guard can probably be removed once // recursing functions are properly handled using e.g. the widen-converge approach. state.hitDepthThreshold = true - if (soundnessMode.isHigh) { + if (highSoundness) { tree.limitToDepth(depthThreshold, StringTreeNode.lb) } else { // In low soundness, we cannot decrease the matched string values by limiting the string tree diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 01dd7a454d..0088eb1124 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -172,7 +172,7 @@ private[string] case class MethodParameterContextStringAnalysisState( def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def currentTreeUB(implicit soundnessMode: SoundnessMode): StringTreeNode = { + def currentTreeUB(implicit highSoundness: HighSoundness): StringTreeNode = { var paramOptions = _methodToEntityMapping.keys.toSeq .sortBy(_.id) .flatMap { dm => @@ -182,7 +182,7 @@ private[string] case class MethodParameterContextStringAnalysisState( .map(_.ub.tree) } - if (soundnessMode.isHigh && ( + if (highSoundness && ( _discoveredUnknownTAC || _callersDependee.exists(cd => cd.hasUBP && cd.ub.hasCallersWithUnknownContext) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index d2b7cd6638..0121711dab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -28,10 +28,10 @@ trait StringInterpreter { */ def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult - def failureTree(implicit soundnessMode: SoundnessMode): StringTreeNode = + def failureTree(implicit highSoundness: HighSoundness): StringTreeNode = StringInterpreter.failureTree - def failure(v: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + def failure(v: PV)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = StringInterpreter.failure(v) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = @@ -46,15 +46,15 @@ trait StringInterpreter { object StringInterpreter { - def failureTree(implicit soundnessMode: SoundnessMode): StringTreeNode = { - if (soundnessMode.isHigh) StringTreeNode.lb + def failureTree(implicit highSoundness: HighSoundness): StringTreeNode = { + if (highSoundness) StringTreeNode.lb else StringTreeNode.ub } - def failure(v: V)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + def failure(v: V)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = failure(v.toPersistentForm(state.tac.stmts)) - def failure(pv: PV)(implicit state: InterpretationState, soundnessMode: SoundnessMode): Result = + def failure(pv: PV)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, pv, failureTree)) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala index d29952a27b..87b6d98fba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala @@ -21,24 +21,24 @@ trait UniversalStringConfig { private final val ConfigLogCategory = "analysis configuration - string analysis - universal" - implicit val soundnessMode: SoundnessMode = { - val mode = + implicit val highSoundness: HighSoundness = { + val isHighSoundness = try { - SoundnessMode(project.config.getBoolean(UniversalStringConfig.SoundnessModeConfigKey)) + project.config.getBoolean(UniversalStringConfig.HighSoundnessConfigKey) } catch { case t: Throwable => logOnce { - Error(ConfigLogCategory, s"couldn't read: ${UniversalStringConfig.SoundnessModeConfigKey}", t) + Error(ConfigLogCategory, s"couldn't read: ${UniversalStringConfig.HighSoundnessConfigKey}", t) } - SoundnessMode(false) + false } - logOnce(Info(ConfigLogCategory, "using soundness mode " + mode)) - mode + logOnce(Info(ConfigLogCategory, s"using ${if (isHighSoundness) "high" else "low"} soundness mode")) + isHighSoundness } } object UniversalStringConfig { - final val SoundnessModeConfigKey = "org.opalj.fpcf.analyses.string.highSoundness" + final val HighSoundnessConfigKey = "org.opalj.fpcf.analyses.string.highSoundness" } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 755b24bb31..672fe3f64e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -10,7 +10,6 @@ import scala.collection.mutable import org.opalj.br.fpcf.properties.string.StringTreeDynamicString import org.opalj.br.fpcf.properties.string.StringTreeInvalidElement -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment @@ -20,7 +19,7 @@ import scalax.collection.GraphTraversal.Parameters class DataFlowAnalysis( private val controlTree: ControlTree, private val superFlowGraph: SuperFlowGraph, - private val soundnessMode: SoundnessMode + private val highSoundness: HighSoundness ) { private val _nodeOrderings = mutable.Map.empty[FlowGraphNode, Seq[SuperFlowGraph#NodeT]] @@ -128,7 +127,7 @@ class DataFlowAnalysis( def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { val resultEnv = pipe(entry, env) - if (resultEnv != env && soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + if (resultEnv != env && highSoundness) env.updateAll(StringTreeDynamicString) else resultEnv } @@ -147,7 +146,7 @@ class DataFlowAnalysis( resultEnv = pipe(currentNode.outer, resultEnv) } - if (resultEnv != envAfterEntry && soundnessMode.isHigh) envAfterEntry.updateAll(StringTreeDynamicString) + if (resultEnv != envAfterEntry && highSoundness) envAfterEntry.updateAll(StringTreeDynamicString) else resultEnv } @@ -166,7 +165,7 @@ class DataFlowAnalysis( ) if (isCyclic) { - if (soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + if (highSoundness) env.updateAll(StringTreeDynamicString) else env.updateAll(StringTreeInvalidElement) } else { // Handle resulting acyclic region @@ -175,7 +174,7 @@ class DataFlowAnalysis( removedBackEdgesGraph.nodes.toSet, entry ) - if (resultEnv != env && soundnessMode.isHigh) env.updateAll(StringTreeDynamicString) + if (resultEnv != env && highSoundness) env.updateAll(StringTreeDynamicString) else resultEnv } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index bbd9835b2e..4c648b12c2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -74,7 +74,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn // No TAC available, e.g., because the method has no body Result( state.entity, - if (soundnessMode.isHigh) MethodStringFlow.lb + if (highSoundness) MethodStringFlow.lb else MethodStringFlow.ub ) } else { @@ -97,7 +97,7 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn StructuralAnalysis.analyze(state.flowGraph, FlowGraph.entry) state.superFlowGraph = superFlowGraph state.controlTree = controlTree - state.flowAnalysis = new DataFlowAnalysis(state.controlTree, state.superFlowGraph, soundnessMode) + state.flowAnalysis = new DataFlowAnalysis(state.controlTree, state.superFlowGraph, highSoundness) state.flowGraph.nodes.toOuter.foreach { case Statement(pc) if pc >= 0 => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala index f4ba408126..2e05f11ad8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala @@ -72,7 +72,7 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var } private var _startEnv: StringTreeEnvironment = StringTreeEnvironment(Map.empty) - def getStartEnvAndReset(implicit soundnessMode: SoundnessMode): StringTreeEnvironment = { + def getStartEnvAndReset(implicit highSoundness: HighSoundness): StringTreeEnvironment = { if (pcToWebChangeMapping.exists(_._2)) { val webs = getWebs val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index 99c545586e..32b57acd4a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * @author Maximilian Rüsch */ case class BinaryExprInterpreter()( - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends AssignmentBasedStringInterpreter { override type E = BinaryExpr[V] @@ -38,11 +38,11 @@ case class BinaryExprInterpreter()( ): ProperPropertyComputationResult = { // IMPROVE Use the underlying domain to retrieve the result of such expressions if possible in low soundness mode computeFinalResult(expr.cTpe match { - case ComputationalTypeInt if soundnessMode.isHigh => + case ComputationalTypeInt if highSoundness => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicInt) case ComputationalTypeInt => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeNode.ub) - case ComputationalTypeFloat if soundnessMode.isHigh => + case ComputationalTypeFloat if highSoundness => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeDynamicFloat) case ComputationalTypeFloat => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeNode.ub) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 44e77b11c8..aa56e02684 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -35,7 +35,7 @@ trait L1FunctionCallInterpreter override type E <: FunctionCall[V] implicit val ps: PropertyStore - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness type CallState <: FunctionCallState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index deaaf762a2..a9b4c9765e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -10,7 +10,6 @@ package interpretation import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.TACAI @@ -22,7 +21,7 @@ import org.opalj.tac.fpcf.properties.TACAI case class L1NonVirtualFunctionCallInterpreter()( implicit val p: SomeProject, implicit val ps: PropertyStore, - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends AssignmentLikeBasedStringInterpreter with L1FunctionCallInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index aefb48d582..3ed25437db 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -18,7 +18,7 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment * @author Maximilian Rüsch */ case class L1NonVirtualMethodCallInterpreter()( - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends StringInterpreter { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 9e37237740..0da1b83174 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -12,7 +12,6 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.string.StringTreeConst import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.StringFlowFunction @@ -26,7 +25,7 @@ case class L1StaticFunctionCallInterpreter()( override val p: SomeProject, override val ps: PropertyStore, override val project: SomeProject, - val soundnessMode: SoundnessMode + val highSoundness: HighSoundness ) extends AssignmentBasedStringInterpreter with L1ArbitraryStaticFunctionCallInterpreter with L1StringValueOfFunctionCallInterpreter @@ -54,7 +53,6 @@ private[string] trait L1ArbitraryStaticFunctionCallInterpreter with L1FunctionCallInterpreter { implicit val p: SomeProject - implicit val soundnessMode: SoundnessMode override type E <: StaticFunctionCall[V] override type CallState = FunctionCallState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index 65baf3fc6d..ca864a9fb3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -21,7 +21,6 @@ import org.opalj.br.fpcf.properties.string.StringTreeDynamicFloat import org.opalj.br.fpcf.properties.string.StringTreeDynamicInt import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment @@ -33,7 +32,7 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter( - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends AssignmentLikeBasedStringInterpreter with L1ArbitraryVirtualFunctionCallInterpreter with L1AppendCallInterpreter @@ -60,14 +59,14 @@ class L1VirtualFunctionCallInterpreter( computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, at.get, - if (soundnessMode.isHigh) StringTreeDynamicInt + if (highSoundness) StringTreeDynamicInt else StringTreeNode.ub )) case FloatType | DoubleType if at.isDefined => computeFinalResult(StringFlowFunctionProperty.constForVariableAt( state.pc, at.get, - if (soundnessMode.isHigh) StringTreeDynamicFloat + if (highSoundness) StringTreeDynamicFloat else StringTreeNode.ub )) case _ if at.isDefined => @@ -102,7 +101,7 @@ class L1VirtualFunctionCallInterpreter( private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness protected def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState @@ -114,7 +113,7 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends Assignme */ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { - val soundnessMode: SoundnessMode + val highSoundness: HighSoundness override type E = VirtualFunctionCall[V] @@ -143,7 +142,7 @@ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringI if (valueState.constancyLevel == StringConstancyLevel.Constant) { valueState } else { - if (soundnessMode.isHigh) StringTreeDynamicFloat + if (highSoundness) StringTreeDynamicFloat else StringTreeNode.ub } case _ => @@ -163,7 +162,7 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri override type E <: VirtualFunctionCall[V] - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit state: InterpretationState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 30c3ba2d75..6da2846b1e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -19,7 +19,7 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ case class L1VirtualMethodCallInterpreter()( - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends StringInterpreter { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index 64cb34a605..f6e07a06e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -20,7 +20,6 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1FunctionCallInterpreter @@ -39,7 +38,7 @@ class L2VirtualFunctionCallInterpreter( implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider, implicit val project: SomeProject, - override implicit val soundnessMode: SoundnessMode + override implicit val highSoundness: HighSoundness ) extends L1VirtualFunctionCallInterpreter with StringInterpreter with L1SystemPropertiesInterpreter @@ -62,7 +61,6 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi implicit val ps: PropertyStore implicit val contextProvider: ContextProvider - implicit val soundnessMode: SoundnessMode override type CallState = CalleeDepender @@ -117,7 +115,7 @@ private[string] trait L2ArbitraryVirtualFunctionCallInterpreter extends L1Functi callState.calleeDependee = eps.asInstanceOf[EOptionP[DefinedMethod, Callees]] if (newMethods.isEmpty && callState.calleeMethods.isEmpty && eps.isFinal) { - failure(callState.target)(state, soundnessMode) + failure(callState.target)(state, highSoundness) } else { for { method <- newMethods diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index e4d2ba2cde..698006b5a3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -28,7 +28,6 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.SomeEOptionP import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP -import org.opalj.tac.fpcf.analyses.string.SoundnessMode import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty @@ -44,7 +43,7 @@ class L3FieldReadInterpreter( implicit val project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider, - implicit val soundnessMode: SoundnessMode + implicit val highSoundness: HighSoundness ) extends AssignmentBasedStringInterpreter { override type E = FieldRead[V] @@ -151,7 +150,7 @@ class L3FieldReadInterpreter( accessState: FieldReadState, state: InterpretationState ): ProperPropertyComputationResult = { - if (accessState.hasWriteInSameMethod && soundnessMode.isHigh) { + if (accessState.hasWriteInSameMethod && highSoundness) { // We cannot handle writes to a field that is read in the same method at the moment as the flow functions do // not capture field state. This can be improved upon in the future. computeFinalResult(StringFlowFunctionProperty.lb(state.pc, accessState.target)) @@ -174,7 +173,7 @@ class L3FieldReadInterpreter( trees = trees :+ StringTreeNull } - if (accessState.hasUnresolvableAccess && soundnessMode.isHigh) { + if (accessState.hasUnresolvableAccess && highSoundness) { trees = trees :+ StringTreeNode.lb } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index d55bdc5ce6..2e888ec8b7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -13,6 +13,7 @@ import org.opalj.br.fpcf.properties.Context */ package object string { + type HighSoundness = Boolean type TAC = TACode[TACMethodParameter, V] private[string] case class VariableDefinition(pc: Int, pv: PV, m: Method) From 90339a6b85a24d3a216b80f225daadb3982776e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 17 Sep 2024 00:53:02 +0200 Subject: [PATCH 571/583] Add tests for missing configurations --- .../opalj/fpcf/fixtures/string/Complex.java | 6 +++-- .../string/ExceptionalControlStructures.java | 27 ++++++++++++------- .../string/SimpleStringBuilderOps.java | 1 + .../org/opalj/fpcf/StringAnalysisTest.scala | 21 ++++++++++++++- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java index 6f08032833..df142e9f6d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java @@ -19,9 +19,11 @@ public void analyzeString(String s) {} /** * Taken from com.sun.javafx.property.PropertyReference#reflect. */ + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "get-Hello, World-java.lang.Runtime") + @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(get-.*|get-Hello, World-java.lang.Runtime)") @Failure(n = 0, levels = Level.L0) + @Invalid(n = 0, levels = Level.L1, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.L1, soundness = SoundnessMode.HIGH, value = "(get-.*|get-Hello, World-.*)") - @PartiallyConstant(n = 0, levels = Level.L2, soundness = SoundnessMode.HIGH, value = "(get-.*|get-Hello, World-java.lang.Runtime)") public void complexDependencyResolve(String s, Class clazz) { String properName = s.length() == 1 ? s.substring(0, 1) : getHelloWorld() + "-" + getRuntimeClassName(); @@ -73,7 +75,7 @@ public void unknownCharValue() { } @Failure(n = 0, levels = { Level.L0, Level.L1 }) - @Constant(n = 0, levels = Level.L2, value = "value") + @Constant(n = 0, levels = { Level.L2, Level.L3 }, value = "value") public String cyclicDependencyTest(String s) { String value = getProperty(s); analyzeString(value); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java index 9771b8d4cc..61142ccac0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java @@ -17,12 +17,15 @@ public class ExceptionalControlStructures { public void analyzeString(String s) {} // Multiple calls to "analyzeString" are generated by the Java compiler, hence we have multiple definitions - @Failure(n = 0, levels = Level.L0) - @Failure(n = 1, levels = Level.L0) - @Failure(n = 2, levels = Level.L0) + @Invalid(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "File Content:.*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "File Content:") @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "File Content:") @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(File Content:|File Content:.*)") + @Failure(n = 2, levels = Level.L0) public void tryFinally(String filename) { StringBuilder sb = new StringBuilder("File Content:"); try { @@ -34,18 +37,21 @@ public void tryFinally(String filename) { } } - @Failure(n = 0, levels = Level.L0) - @Failure(n = 1, levels = Level.L0) - @Failure(n = 2, levels = Level.L0) + @Invalid(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "=====.*") + @Failure(n = 0, levels = Level.L0) // Exception case without own thrown exception + @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "==========") @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(=====.*=====|==========)") + @Failure(n = 1, levels = Level.L0) // The following cases are detected: // 1. Code around Files.readAllBytes failing, throwing a non-exception Throwable -> no append (Pos 1) // 2. Code around Files.readAllBytes failing, throwing an exception Throwable -> exception case append (Pos 4) // 3. First append succeeds, throws no exception -> only first append (Pos 2) // 4. First append is executed but throws an exception Throwable -> both appends (Pos 3) + @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "(=====|==========)") @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(=====|=====.*|=====.*=====|==========)") + @Failure(n = 2, levels = Level.L0) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); try { @@ -58,12 +64,15 @@ public void tryCatchFinally(String filename) { } } - @Failure(n = 0, levels = Level.L0) - @Failure(n = 1, levels = Level.L0) - @Failure(n = 2, levels = Level.L0) + @Invalid(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "BOS:.*") + @Failure(n = 0, levels = Level.L0) + @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "BOS::EOS") @PartiallyConstant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS:.*:EOS|BOS::EOS)") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "BOS::EOS") @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(BOS:.*:EOS|BOS::EOS)") + @Failure(n = 2, levels = Level.L0) public void tryCatchFinallyWithThrowable(String filename) { StringBuilder sb = new StringBuilder("BOS:"); try { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java index b2b7317c14..490e6fb6da 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java @@ -175,6 +175,7 @@ public void crissCrossExample(String className) { analyzeString(sbRun.toString()); } + @Invalid(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.TRUTH, value = "File Content:.*", soundness = SoundnessMode.HIGH) @Failure(n = 0, levels = Level.L0) public void withUnknownAppendSource(String filename) throws IOException { 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 8fe203c910..82ee206277 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 @@ -41,6 +41,7 @@ import org.opalj.fpcf.properties.string_analysis.Level import org.opalj.fpcf.properties.string_analysis.PartiallyConstant import org.opalj.fpcf.properties.string_analysis.PartiallyConstants import org.opalj.fpcf.properties.string_analysis.SoundnessMode +import org.opalj.log.OPALLogger import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV import org.opalj.tac.TACMethodParameter @@ -166,11 +167,23 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { val m2e = entities.groupBy(_._2).iterator.map(e => e._1 -> (e._1, e._2.map(k => k._1)) ).toMap + + var detectedMissingAnnotations = false // As entity, we need not the method but a tuple (PUVar, Method), thus this transformation - methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => + val eas = methodsWithAnnotations(project).filter(am => m2e.contains(am._1)).flatMap { am => val annotationsByIndex = getCheckableAnnotationsByIndex(project, am._3) m2e(am._1)._2.zipWithIndex.map { case (vc, index) => + // Ensure every test that is annotated for at least one configuration is annotated for all + // configurations so we do not miss tests when adding new levels. + if (annotationsByIndex(index).isEmpty) { + OPALLogger.error( + "string analysis test setup", + s"Could not find annotations to check for #$index of ${am._1.toJava.substring(24)}" + )(project.logContext) + detectedMissingAnnotations = true + } + Tuple3( vc, { s: String => s"${am._2(s)} (#$index)" }, @@ -178,6 +191,12 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { ) } } + + if (detectedMissingAnnotations) { + throw new IllegalStateException("Detected missing tests for this configuration!") + } + + eas } private def getCheckableAnnotationsByIndex(project: Project[URL], as: Annotations): Map[Int, Annotations] = { From fceb1f50b14eadf9e6a28423f94204553570208c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Tue, 17 Sep 2024 01:03:18 +0200 Subject: [PATCH 572/583] Add tests for missing configurations --- .../src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 3 +++ 1 file changed, 3 insertions(+) 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 82ee206277..0031146202 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 @@ -61,6 +61,9 @@ import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringAnalysis import org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringAnalysis import org.opalj.tac.fpcf.analyses.systemproperties.TriggeredSystemPropertiesAnalysisScheduler +// IMPROVE the test runner structure is far from optimal and could be reduced down to a simple test matrix. This however +// would require generating the wrapping "describes" and tag the tests using e.g. "asTagged" to be able to filter them +// during runs. sealed abstract class StringAnalysisTest extends PropertiesTest { // The name of the method from which to extract PUVars to analyze. From ce6bc7f74909bca93bb6d9243ed38c7fa1e4913b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 21:59:15 +0200 Subject: [PATCH 573/583] Document all string analysis tests --- .../opalj/fpcf/fixtures/string/ArrayOps.java | 10 +- .../opalj/fpcf/fixtures/string/Complex.java | 32 ++- .../string/ExceptionalControlStructures.java | 6 +- .../{External.java => FieldAccesses.java} | 63 +---- .../fpcf/fixtures/string/FunctionCalls.java | 19 ++ .../fixtures/string/FunctionParameter.java | 58 +++++ .../fpcf/fixtures/string/Integration.java | 57 ----- .../org/opalj/fpcf/fixtures/string/Loops.java | 4 + .../opalj/fpcf/fixtures/string/Result.java | 30 +++ .../string/SimpleControlStructures.java | 135 ++++++++++ .../string/SimpleStringBuilderOps.java | 3 + .../fpcf/fixtures/string/SimpleStringOps.java | 240 +++++++----------- .../fixtures/string/SystemProperties.java | 27 ++ 13 files changed, 409 insertions(+), 275 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/{External.java => FieldAccesses.java} (60%) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java index 0eb50bdac7..de8edd33cd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java @@ -5,6 +5,9 @@ import org.opalj.fpcf.properties.string_analysis.*; /** + * Various tests that test compatibility with array operations (mainly reads with specific array indexes). + * Currently, the string analysis does not support array operations and thus fails for every level. + * * @see SimpleStringOps */ public class ArrayOps { @@ -28,14 +31,17 @@ public void fromStringArray(int index) { } } - @Dynamic(n = 0, levels = Level.TRUTH, value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, + value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer)") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, + value = "(java.lang.Object|java.lang.Runtime|java.lang.Integer|.*)") @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }, reason = "arrays are not supported") public void arrayStaticAndVirtualFunctionCalls(int i) { String[] classes = { "java.lang.Object", getRuntimeClassName(), StringProvider.getFQClassNameWithStringBuilder("java.lang", "Integer"), - System.getProperty("SomeClass") + System.clearProperty("SomeClass") }; analyzeString(classes[i]); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java index df142e9f6d..0d7ffd0602 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java @@ -3,10 +3,17 @@ import org.opalj.fpcf.properties.string_analysis.*; +import java.io.File; +import java.io.FileNotFoundException; import java.lang.reflect.Method; import java.util.Random; +import java.util.Scanner; /** + * Various tests that test certain complex string analysis scenarios which were either constructed or extracted from the + * JDK. Such tests should combine multiple string analysis techniques, the most common are interprocedurality and + * control flow sensitivity. + * * @see SimpleStringOps */ public class Complex { @@ -40,12 +47,12 @@ public void complexDependencyResolve(String s, Class clazz) { /** * Taken from com.sun.prism.impl.ps.BaseShaderContext#getPaintShader and slightly adapted */ - @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintname(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") + @Constant(n = 0, levels = Level.TRUTH, value = "Hello, World_paintName(_PAD|_REFLECT|_REPEAT)?(_AlphaTest)?") @Failure(n = 0, levels = Level.L0) // or-cases are currently not collapsed into simpler conditionals / or-cases using prefix checking - @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, value = "((Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)_AlphaTest|Hello, World_paintname|Hello, World_paintname_PAD|Hello, World_paintname_REFLECT|Hello, World_paintname_REPEAT)") + @Constant(n = 0, levels = { Level.L1, Level.L2, Level.L3 }, value = "((Hello, World_paintName|Hello, World_paintName_PAD|Hello, World_paintName_REFLECT|Hello, World_paintName_REPEAT)_AlphaTest|Hello, World_paintName|Hello, World_paintName_PAD|Hello, World_paintName_REFLECT|Hello, World_paintName_REPEAT)") public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { - String shaderName = getHelloWorld() + "_" + "paintname"; + String shaderName = getHelloWorld() + "_" + "paintName"; if (getPaintType) { if (spreadMethod == 0) { shaderName = shaderName + "_PAD"; @@ -82,6 +89,25 @@ public String cyclicDependencyTest(String s) { return value; } + /** + * Methods are called that return a string but are not within this project => cannot / will not interpret + */ + @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") + @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }) + @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) + @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") + public void methodsOutOfScopeTest() 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()); + + analyzeString(System.clearProperty("os.version")); + } + private String getProperty(String name) { if (name == null) { return cyclicDependencyTest("default"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java index 61142ccac0..9e29facbfc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java @@ -7,6 +7,11 @@ import java.nio.file.Paths; /** + * Various tests that test general compatibility with the control flow generated by try-catch(-finally) statements. + *

    + * Since this type of statement can compile to multiple instances of calls that were initially defined in "finally" + * blocks, more sink calls may be generated than visible in the source code. + * * @see SimpleStringOps */ public class ExceptionalControlStructures { @@ -16,7 +21,6 @@ public class ExceptionalControlStructures { */ public void analyzeString(String s) {} - // Multiple calls to "analyzeString" are generated by the Java compiler, hence we have multiple definitions @Invalid(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW) @PartiallyConstant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "File Content:.*") @Failure(n = 0, levels = Level.L0) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java similarity index 60% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java index e3f8dced78..ade346512c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/External.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java @@ -3,16 +3,15 @@ import org.opalj.fpcf.properties.string_analysis.*; -import javax.management.remote.rmi.RMIServer; -import java.io.File; -import java.io.FileNotFoundException; import java.util.Random; -import java.util.Scanner; /** + * Various tests that test general compatibility with the field access information FPCF property, e.g. being able to + * analyze field reads and writes across method boundaries. + * * @see SimpleStringOps */ -public class External { +public class FieldAccesses { protected String nonFinalNonStaticField = "private l0 non-final string field"; public static String nonFinalStaticField = "will not be revealed here"; @@ -33,7 +32,7 @@ public class External { } } - public External(float e) { + public FieldAccesses(float e) { fieldWithConstructorInit = "initialized by constructor"; fieldWithConstructorParameterInit = e; } @@ -114,56 +113,4 @@ public void fieldWithNoWriteTest() { public void nonSupportedFieldTypeRead() { analyzeString(unsupportedTypeField.toString()); } - - public void parameterCaller() { - this.parameterRead("some-param-value", new StringBuilder("some-other-param-value")); - } - - @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-param-value") - @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-param-value)", - reason = "method is an entry point and thus has callers with unknown context") - @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-other-param-value") - @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-other-param-value)", - reason = "method is an entry point and thus has callers with unknown context") - @Failure(n = 1, levels = Level.L0) - @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value") - @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)", - reason = "method is an entry point and thus has callers with unknown context") - @Failure(n = 2, levels = Level.L0) - @Constant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value-some-other-param-value") - @PartiallyConstant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)-(.*|some-other-param-value)", - reason = "method is an entry point and thus has callers with unknown context") - @Failure(n = 3, levels = Level.L0) - 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("-"); - sb.append(sbValue.toString()); - analyzeString(sb.toString()); - } - - /** - * Methods are called that return a string but are not within this project => cannot / will not interpret - */ - @Dynamic(n = 0, levels = Level.TRUTH, value = "(.*)*") - @Failure(n = 0, levels = { Level.L0, Level.L1, Level.L2, Level.L3 }) - @Invalid(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW) - @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = ".*") - public void methodsOutOfScopeTest() 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()); - - analyzeString(System.clearProperty("os.version")); - } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java index 7a49dbe552..6ea20ca614 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java @@ -1,10 +1,16 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; +import org.opalj.fpcf.fixtures.string.tools.GreetingService; +import org.opalj.fpcf.fixtures.string.tools.HelloGreeting; import org.opalj.fpcf.fixtures.string.tools.StringProvider; import org.opalj.fpcf.properties.string_analysis.*; /** + * Various tests that test specific compatibility of the different levels of the string analysis with resolving + * different types of function calls and their impact on the analyzed strings. As an example, arbitrary virtual function + * calls may be analyzable in one level of the string analysis but not another. + * * @see SimpleStringOps */ public class FunctionCalls { @@ -109,6 +115,19 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @Constant(n = 0, levels = Level.TRUTH, value = "Hello World") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) + public void knownHierarchyInstanceTest() { + GreetingService gs = new HelloGreeting(); + analyzeString(gs.getGreeting("World")); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(Hello|Hello World)") + @Failure(n = 0, levels = { Level.L0, Level.L1 }) + public void unknownHierarchyInstanceTest(GreetingService greetingService) { + analyzeString(greetingService.getGreeting("World")); + } + /** * A case where the single valid return value of the called function can be resolved without calling the function. */ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java new file mode 100644 index 0000000000..ad936cff91 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java @@ -0,0 +1,58 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string; + +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * Various tests that test whether detection of needing to resolve parameters for a given string works or not. Note that + * there is a separate test file for various function calls that also partially covers this detection. + * + * @see FunctionCalls + * @see SimpleStringOps + */ +public class FunctionParameter { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + public void parameterCaller() { + this.parameterRead("some-param-value", new StringBuilder("some-other-param-value")); + } + + @Constant(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-param-value") + @Dynamic(n = 0, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-param-value)", + reason = "method is an entry point and thus has callers with unknown context") + @Constant(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "some-other-param-value") + @Dynamic(n = 1, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "(.*|some-other-param-value)", + reason = "method is an entry point and thus has callers with unknown context") + @Failure(n = 1, levels = Level.L0) + @Constant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value") + @PartiallyConstant(n = 2, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)", + reason = "method is an entry point and thus has callers with unknown context") + @Failure(n = 2, levels = Level.L0) + @Constant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.LOW, value = "value=some-param-value-some-other-param-value") + @PartiallyConstant(n = 3, levels = Level.TRUTH, soundness = SoundnessMode.HIGH, value = "value=(.*|some-param-value)-(.*|some-other-param-value)", + reason = "method is an entry point and thus has callers with unknown context") + @Failure(n = 3, levels = Level.L0) + 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("-"); + sb.append(sbValue.toString()); + analyzeString(sb.toString()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") + public void noParameterInformationRequiredTest(String s) { + System.out.println(s); + analyzeString("java.lang.String"); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java deleted file mode 100644 index fc9157a217..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Integration.java +++ /dev/null @@ -1,57 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string; - -import org.opalj.fpcf.fixtures.string.tools.GreetingService; -import org.opalj.fpcf.fixtures.string.tools.HelloGreeting; -import org.opalj.fpcf.properties.string_analysis.*; - -/** - * @see SimpleStringOps - */ -public class Integration { - - /** - * Serves as the sink for string variables to be analyzed. - */ - public void analyzeString(String s) {} - - @Constant(n = 0, levels = Level.TRUTH, value = "java.lang.String") - public void noCallersInformationRequiredTest(String s) { - System.out.println(s); - analyzeString("java.lang.String"); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "some.test.value") - @Failure(n = 0, levels = Level.L0) - public void systemPropertiesIntegrationTest() { - System.setProperty("some.test.property", "some.test.value"); - String s = System.getProperty("some.test.property"); - analyzeString(s); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "Hello World") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) - public void knownHierarchyInstanceTest() { - GreetingService gs = new HelloGreeting(); - analyzeString(gs.getGreeting("World")); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(Hello|Hello World)") - @Failure(n = 0, levels = { Level.L0, Level.L1 }) - public void unknownHierarchyInstanceTest(GreetingService greetingService) { - analyzeString(greetingService.getGreeting("World")); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "\\[B") - @Constant(n = 1, levels = Level.TRUTH, value = "\\[Ljava.lang.String;") - @Constant(n = 2, levels = Level.TRUTH, value = "\\[\\[Lsun.security.pkcs.SignerInfo;") - @Constant(n = 3, levels = Level.TRUTH, value = "US\\$") - @Constant(n = 4, levels = Level.TRUTH, value = "US\\\\") - public void regexCompilableTest() { - analyzeString("[B"); - analyzeString("[Ljava.lang.String;"); - analyzeString("[[Lsun.security.pkcs.SignerInfo;"); - analyzeString("US$"); - analyzeString("US\\"); - } -} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java index a1001a3d84..284195fad4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java @@ -8,6 +8,10 @@ import java.util.Random; /** + * Various tests that contain some kind of loops which modify string variables, requiring data flow analysis to resolve + * these values or at least approximate them. Currently, the string analysis either only interprets the loop body once + * (in low-soundness mode) or over-approximates with "any string" (in high-soundness mode). + * * @see SimpleStringOps */ public class Loops { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java new file mode 100644 index 0000000000..1c6dd5aab5 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java @@ -0,0 +1,30 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string; + +import org.opalj.fpcf.properties.string_analysis.*; + +/** + * Tests compatibility of the results of the string analysis with e.g. being compiled to a regex string. + * + * @see SimpleStringOps + */ +public class Result { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Constant(n = 0, levels = Level.TRUTH, value = "\\[B") + @Constant(n = 1, levels = Level.TRUTH, value = "\\[Ljava.lang.String;") + @Constant(n = 2, levels = Level.TRUTH, value = "\\[\\[Lsun.security.pkcs.SignerInfo;") + @Constant(n = 3, levels = Level.TRUTH, value = "US\\$") + @Constant(n = 4, levels = Level.TRUTH, value = "US\\\\") + public void regexCompilableTest() { + analyzeString("[B"); + analyzeString("[Ljava.lang.String;"); + analyzeString("[[Lsun.security.pkcs.SignerInfo;"); + analyzeString("US$"); + analyzeString("US\\"); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java index d6b6ee9fe6..f6755d3fcc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java @@ -6,6 +6,8 @@ import java.util.Random; /** + * Various tests that test compatibility of the data flow analysis with simple control structures like if-statements. + * * @see SimpleStringOps */ public class SimpleControlStructures { @@ -128,4 +130,137 @@ public void ifConditionAppendsToString(String className) { } analyzeString(sb.toString()); } + + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") + @Failure(n = 0, levels = Level.L0) + public void switchRelevantAndIrrelevant(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()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad)") + @Failure(n = 0, levels = Level.L0) + public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") + @Failure(n = 0, levels = Level.L0) + public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 2: + break; + case 3: + break; + default: + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad)") + @Failure(n = 0, levels = Level.L0) + public void switchRelevantWithRelevantDefault(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + default: + sb.append("d"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad|af)") + @Failure(n = 0, levels = Level.L0) + public void switchNestedNoNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + } + break; + default: + sb.append("f"); + break; + } + analyzeString(sb.toString()); + } + + @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad|ae|af)") + @Failure(n = 0, levels = Level.L0) + public void switchNestedWithNestedDefault(int value, int value2) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + switch (value2) { + case 0: + sb.append("c"); + break; + case 1: + sb.append("d"); + break; + default: + sb.append("e"); + break; + } + break; + default: + sb.append("f"); + break; + } + analyzeString(sb.toString()); + } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java index 490e6fb6da..aa6fe13cc2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java @@ -8,6 +8,9 @@ import java.nio.file.Paths; /** + * Various tests that test compatibility with selected methods defined on string builders and string buffers, such as + * append, reset etc. + * * @see SimpleStringOps */ public class SimpleStringBuilderOps { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java index 0e9419e2e6..02498e4aa9 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java @@ -6,30 +6,95 @@ /** * All files in this package define various tests for the string analysis. 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.
    • - *
    • - * 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. - *
    • + *
    • + * In order to trigger the analysis for a particular string variable, call the analyzeString method with the + * variable to be analyzed. Multiple calls to the sink analyzeString within the same test method are allowed. + *
    • + *
    • + * For a given sink call, the expected string and its constancy can be defined using one of these annotations: + *
        + *
      • + * {@link Invalid} The given string variable does not contain any string analyzable by the string + * analysis. Usually used as a fallback in low-soundness mode. + *
      • + *
      • + * {@link Constant} The given string variable contains only constant strings and its set of possible + * values is thus enumerable within finite time. + *
      • + *
      • + * {@link PartiallyConstant} The given string variable contains strings which have some constant part + * concatenated with some dynamic part. Its set of possible values is constrained but not enumerable + * within finite time. + *
      • + *
      • + * {@link Dynamic} The given string variable contains strings which only consist of dynamic information. + * Its set of possible values may be constrained but is definitely not enumerable within finite time. + * Usually used as a fallback in high-soundness mode. + *
      • + *
      • + * {@link Failure} Combines {@link Invalid} and {@link Dynamic} by generating the former for test runs in + * low-soundness mode and the latter for test runs in high-soundness mode. + *
      • + *
      + *
    • + *
    • + * For each test run configuration (different domain level, different soundness mode, different analysis level) + * exactly one such annotation should be defined for each test function. For every annotation, the following + * information should / can be given: + *
        + *
      • (Required) n = ?: The index of the sink call that this annotation is defined for.
      • + *
      • + * (Required) value = "?": The expected value (see below for format). + * Cannot be defined for {@link Invalid} annotations. + *
      • + *
      • + * (Required) levels = ?: One or multiple of {@link Level} to allow restricting an annotation + * to certain string analysis level configurations. The value {@link Level#TRUTH } may be used to explicitly + * define the ground truth that all test run configurations will fall back to if no more specific annotation + * is found. + *
      • + *
      • + * (Optional) domains = ?: One or multiple of {@link DomainLevel} to allow restricting an + * annotation to certain domain level configurations. + *
      • + *
      • + * (Optional) soundness = ?: One or multiple of {@link SoundnessMode} to allow restricting an + * annotation to certain soundness mode configurations. + *
      • + *
      • + * (Optional) reason = "?": Some reasoning for the given annotation type and value. Not part of + * the test output. + *
      • + *
      + *
    • + *
    • + * Expected values for string variables should be given in a reduced regex format: + *
        + *
      • The asterisk symbol (*) is used to indicate that a string (or part of it) can occur >= 0 times.
      • + *
      • + * 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.
      • + *
      • + * The string "^-?\d+$" represents (positive and negative) integer numbers. This RegExp has been taken from + * www.freeformatter.com/java-regex-tester.html + * 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 + * www.freeformatter.com/java-regex-tester.html + * as of 2019-02-02. + *
      • + *
      + *
    • *
    *

    - * 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. + * This file defines various tests related to simple operations on strings and presence of multiple def sites of such + * strings. * * @author Maximilian Rüsch */ @@ -128,139 +193,6 @@ public void ternaryOperators(boolean flag) { analyzeString(flag ? s1 + s3 : s2); } - @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") - @Failure(n = 0, levels = Level.L0) - public void switchRelevantAndIrrelevant(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()); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad)") - @Failure(n = 0, levels = Level.L0) - public void switchRelevantAndIrrelevantWithRelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - case 2: - break; - case 3: - break; - default: - sb.append("d"); - break; - } - analyzeString(sb.toString()); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac)") - @Failure(n = 0, levels = Level.L0) - public void switchRelevantAndIrrelevantWithIrrelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - case 2: - break; - case 3: - break; - default: - break; - } - analyzeString(sb.toString()); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad)") - @Failure(n = 0, levels = Level.L0) - public void switchRelevantWithRelevantDefault(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - default: - sb.append("d"); - break; - } - analyzeString(sb.toString()); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(a|ab|ac|ad|af)") - @Failure(n = 0, levels = Level.L0) - public void switchNestedNoNestedDefault(int value, int value2) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - switch (value2) { - case 0: - sb.append("c"); - break; - case 1: - sb.append("d"); - break; - } - break; - default: - sb.append("f"); - break; - } - analyzeString(sb.toString()); - } - - @Constant(n = 0, levels = Level.TRUTH, value = "(ab|ac|ad|ae|af)") - @Failure(n = 0, levels = Level.L0) - public void switchNestedWithNestedDefault(int value, int value2) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - switch (value2) { - case 0: - sb.append("c"); - break; - case 1: - sb.append("d"); - break; - default: - sb.append("e"); - break; - } - break; - default: - sb.append("f"); - break; - } - analyzeString(sb.toString()); - } - /** * A more comprehensive case where multiple definition sites have to be considered each with a different string * generation mechanism diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java new file mode 100644 index 0000000000..47cf8ae174 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string; + +import org.opalj.fpcf.properties.string_analysis.Constant; +import org.opalj.fpcf.properties.string_analysis.Failure; +import org.opalj.fpcf.properties.string_analysis.Level; + +/** + * Tests the integration with the system properties FPCF property. + * + * @see SimpleStringOps + */ +public class SystemProperties { + + /** + * Serves as the sink for string variables to be analyzed. + */ + public void analyzeString(String s) {} + + @Constant(n = 0, levels = Level.TRUTH, value = "some.test.value") + @Failure(n = 0, levels = Level.L0) + public void systemPropertiesIntegrationTest() { + System.setProperty("some.test.property", "some.test.value"); + String s = System.getProperty("some.test.property"); + analyzeString(s); + } +} From 63da91648f1e2fe4daafd84ec37bc8c81a9990c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 22:02:30 +0200 Subject: [PATCH 574/583] Rename hierarchy related string analysis test fixtures --- .../opalj/fpcf/fixtures/string/FunctionCalls.java | 12 ++++++------ .../fpcf/fixtures/string/tools/HelloGreeting.java | 11 ----------- .../tools/ParameterDependentStringFactory.java | 11 +++++++++++ ...leHelloGreeting.java => SimpleStringFactory.java} | 4 ++-- .../{GreetingService.java => StringFactory.java} | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/ParameterDependentStringFactory.java rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/{SimpleHelloGreeting.java => SimpleStringFactory.java} (58%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/{GreetingService.java => StringFactory.java} (60%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java index 6ea20ca614..f1ec90e540 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java @@ -1,8 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.fixtures.string.tools.GreetingService; -import org.opalj.fpcf.fixtures.string.tools.HelloGreeting; +import org.opalj.fpcf.fixtures.string.tools.StringFactory; +import org.opalj.fpcf.fixtures.string.tools.ParameterDependentStringFactory; import org.opalj.fpcf.fixtures.string.tools.StringProvider; import org.opalj.fpcf.properties.string_analysis.*; @@ -118,14 +118,14 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { @Constant(n = 0, levels = Level.TRUTH, value = "Hello World") @Failure(n = 0, levels = { Level.L0, Level.L1 }) public void knownHierarchyInstanceTest() { - GreetingService gs = new HelloGreeting(); - analyzeString(gs.getGreeting("World")); + StringFactory sf = new ParameterDependentStringFactory(); + analyzeString(sf.getString("World")); } @Constant(n = 0, levels = Level.TRUTH, value = "(Hello|Hello World)") @Failure(n = 0, levels = { Level.L0, Level.L1 }) - public void unknownHierarchyInstanceTest(GreetingService greetingService) { - analyzeString(greetingService.getGreeting("World")); + public void unknownHierarchyInstanceTest(StringFactory stringFactory) { + analyzeString(stringFactory.getString("World")); } /** diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java deleted file mode 100644 index 26d0e0bbbe..0000000000 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/HelloGreeting.java +++ /dev/null @@ -1,11 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string.tools; - -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/tools/ParameterDependentStringFactory.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/ParameterDependentStringFactory.java new file mode 100644 index 0000000000..198d514828 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/ParameterDependentStringFactory.java @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string.tools; + +public class ParameterDependentStringFactory implements StringFactory { + + @Override + public String getString(String parameter) { + return "Hello " + parameter; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleStringFactory.java similarity index 58% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleStringFactory.java index 85bf0cf3e0..f8fe33378d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleHelloGreeting.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/SimpleStringFactory.java @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string.tools; -public class SimpleHelloGreeting implements GreetingService { +public class SimpleStringFactory implements StringFactory { @Override - public String getGreeting(String name) { + public String getString(String parameter) { return "Hello"; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringFactory.java similarity index 60% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringFactory.java index 0b29615e86..e611cf693f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/GreetingService.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/tools/StringFactory.java @@ -1,9 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string.tools; -public interface GreetingService { +public interface StringFactory { - String getGreeting(String name); + String getString(String parameter); } From bd2450128ce3e677363c9b20c96d640948886def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 22:03:43 +0200 Subject: [PATCH 575/583] Rename test properties packages --- .../opalj/fpcf/fixtures/string/ArrayOps.java | 2 +- .../opalj/fpcf/fixtures/string/Complex.java | 2 +- .../string/ExceptionalControlStructures.java | 2 +- .../fpcf/fixtures/string/FieldAccesses.java | 2 +- .../fpcf/fixtures/string/FunctionCalls.java | 2 +- .../fixtures/string/FunctionParameter.java | 2 +- .../org/opalj/fpcf/fixtures/string/Loops.java | 2 +- .../opalj/fpcf/fixtures/string/Result.java | 2 +- .../string/SimpleControlStructures.java | 2 +- .../string/SimpleStringBuilderOps.java | 2 +- .../fpcf/fixtures/string/SimpleStringOps.java | 2 +- .../fixtures/string/SystemProperties.java | 6 ++--- .../{string_analysis => string}/Constant.java | 2 +- .../Constants.java | 2 +- .../DomainLevel.java | 2 +- .../{string_analysis => string}/Dynamic.java | 2 +- .../{string_analysis => string}/Dynamics.java | 2 +- .../{string_analysis => string}/Failure.java | 2 +- .../{string_analysis => string}/Failures.java | 2 +- .../{string_analysis => string}/Invalid.java | 2 +- .../{string_analysis => string}/Invalids.java | 2 +- .../{string_analysis => string}/Level.java | 2 +- .../PartiallyConstant.java | 2 +- .../PartiallyConstants.java | 2 +- .../SoundnessMode.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 26 +++++++++---------- .../StringMatcher.scala | 2 +- 27 files changed, 41 insertions(+), 41 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Constant.java (93%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Constants.java (81%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/DomainLevel.java (86%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Dynamic.java (93%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Dynamics.java (81%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Failure.java (90%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Failures.java (81%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Invalid.java (92%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Invalids.java (81%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/Level.java (87%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/PartiallyConstant.java (93%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/PartiallyConstants.java (82%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_analysis => string}/SoundnessMode.java (86%) rename DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/{string_analysis => string}/StringMatcher.scala (99%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java index de8edd33cd..9edcc09852 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ArrayOps.java @@ -2,7 +2,7 @@ package org.opalj.fpcf.fixtures.string; import org.opalj.fpcf.fixtures.string.tools.StringProvider; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; /** * Various tests that test compatibility with array operations (mainly reads with specific array indexes). diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java index 0d7ffd0602..5898f48ca0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Complex.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.io.File; import java.io.FileNotFoundException; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java index 9e29facbfc..1c1f396f3f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/ExceptionalControlStructures.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.nio.file.Files; import java.nio.file.Paths; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java index ade346512c..1a194e32b4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FieldAccesses.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.util.Random; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java index f1ec90e540..b681b1cd59 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionCalls.java @@ -4,7 +4,7 @@ import org.opalj.fpcf.fixtures.string.tools.StringFactory; import org.opalj.fpcf.fixtures.string.tools.ParameterDependentStringFactory; import org.opalj.fpcf.fixtures.string.tools.StringProvider; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; /** * Various tests that test specific compatibility of the different levels of the string analysis with resolving diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java index ad936cff91..7d4f9d899a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/FunctionParameter.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; /** * Various tests that test whether detection of needing to resolve parameters for a given string works or not. Note that diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java index 284195fad4..1fb420a1d6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Loops.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java index 1c6dd5aab5..91ccd9a6ab 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/Result.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; /** * Tests compatibility of the results of the string analysis with e.g. being compiled to a regex string. diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java index f6755d3fcc..f4d2fd3e17 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleControlStructures.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.util.Random; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java index aa6fe13cc2..2d10463538 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringBuilderOps.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; import java.io.IOException; import java.nio.file.Files; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java index 02498e4aa9..ce890f5686 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SimpleStringOps.java @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.*; +import org.opalj.fpcf.properties.string.*; /** * All files in this package define various tests for the string analysis. The following things are to be considered diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java index 47cf8ae174..a328619b4c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string/SystemProperties.java @@ -1,9 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string; -import org.opalj.fpcf.properties.string_analysis.Constant; -import org.opalj.fpcf.properties.string_analysis.Failure; -import org.opalj.fpcf.properties.string_analysis.Level; +import org.opalj.fpcf.properties.string.Constant; +import org.opalj.fpcf.properties.string.Failure; +import org.opalj.fpcf.properties.string.Level; /** * Tests the integration with the system properties FPCF property. diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java similarity index 93% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java index 6288f3a593..e4690e566d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import org.opalj.fpcf.properties.PropertyValidator; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java similarity index 81% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java index 3a77c92313..092465146a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Constants.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java similarity index 86% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java index 62e1583ddb..d52675a7a7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/DomainLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; /** * @author Maximilian Rüsch diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java similarity index 93% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java index a6bf417c0b..55f95e05f3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamic.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import org.opalj.fpcf.properties.PropertyValidator; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java similarity index 81% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java index cc5095d25c..e4f1a0622b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Dynamics.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java similarity index 90% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java index d366336416..69da3017ed 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failure.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java similarity index 81% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java index 7e9403d74a..6e0edf86a3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Failures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java similarity index 92% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java index de0f6da434..c1321348d6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalid.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import org.opalj.fpcf.properties.PropertyValidator; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java similarity index 81% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java index 75ad3fa42d..fdcef6e2fb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Invalids.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java similarity index 87% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java index 23449821a3..dc448dc853 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/Level.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; /** * @author Maximilian Rüsch diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java similarity index 93% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java index 8ac14772a7..3a455751db 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import org.opalj.fpcf.properties.PropertyValidator; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java similarity index 82% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java index 170407260d..4f7a2cdaeb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/PartiallyConstants.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java similarity index 86% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java index 2e3d41f8e5..7f5cf4b2de 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/SoundnessMode.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_analysis; +package org.opalj.fpcf.properties.string; /** * @author Maximilian Rüsch 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 0031146202..10017953d3 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 @@ -28,19 +28,19 @@ import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.br.fpcf.properties.string.StringConstancyProperty -import org.opalj.fpcf.properties.string_analysis.Constant -import org.opalj.fpcf.properties.string_analysis.Constants -import org.opalj.fpcf.properties.string_analysis.DomainLevel -import org.opalj.fpcf.properties.string_analysis.Dynamic -import org.opalj.fpcf.properties.string_analysis.Dynamics -import org.opalj.fpcf.properties.string_analysis.Failure -import org.opalj.fpcf.properties.string_analysis.Failures -import org.opalj.fpcf.properties.string_analysis.Invalid -import org.opalj.fpcf.properties.string_analysis.Invalids -import org.opalj.fpcf.properties.string_analysis.Level -import org.opalj.fpcf.properties.string_analysis.PartiallyConstant -import org.opalj.fpcf.properties.string_analysis.PartiallyConstants -import org.opalj.fpcf.properties.string_analysis.SoundnessMode +import org.opalj.fpcf.properties.string.Constant +import org.opalj.fpcf.properties.string.Constants +import org.opalj.fpcf.properties.string.DomainLevel +import org.opalj.fpcf.properties.string.Dynamic +import org.opalj.fpcf.properties.string.Dynamics +import org.opalj.fpcf.properties.string.Failure +import org.opalj.fpcf.properties.string.Failures +import org.opalj.fpcf.properties.string.Invalid +import org.opalj.fpcf.properties.string.Invalids +import org.opalj.fpcf.properties.string.Level +import org.opalj.fpcf.properties.string.PartiallyConstant +import org.opalj.fpcf.properties.string.PartiallyConstants +import org.opalj.fpcf.properties.string.SoundnessMode import org.opalj.log.OPALLogger import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.PV diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala similarity index 99% rename from DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala rename to DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala index 200ce56f6f..fc33772727 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala @@ -2,7 +2,7 @@ package org.opalj package fpcf package properties -package string_analysis +package string import java.util.regex.Pattern import scala.util.Try From a5584c11c1ce2e792df9cc4c5270b612c728f50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 22:05:50 +0200 Subject: [PATCH 576/583] Add some documentation to test annotations --- .../test/java/org/opalj/fpcf/properties/string/Constant.java | 4 ++++ .../test/java/org/opalj/fpcf/properties/string/Constants.java | 4 ++++ .../java/org/opalj/fpcf/properties/string/DomainLevel.java | 1 + .../test/java/org/opalj/fpcf/properties/string/Dynamic.java | 4 ++++ .../test/java/org/opalj/fpcf/properties/string/Dynamics.java | 4 ++++ .../test/java/org/opalj/fpcf/properties/string/Failure.java | 3 +++ .../test/java/org/opalj/fpcf/properties/string/Failures.java | 4 ++++ .../test/java/org/opalj/fpcf/properties/string/Invalid.java | 4 ++++ .../test/java/org/opalj/fpcf/properties/string/Invalids.java | 4 ++++ .../src/test/java/org/opalj/fpcf/properties/string/Level.java | 1 + .../org/opalj/fpcf/properties/string/PartiallyConstant.java | 4 ++++ .../org/opalj/fpcf/properties/string/PartiallyConstants.java | 4 ++++ .../java/org/opalj/fpcf/properties/string/SoundnessMode.java | 1 + .../org/opalj/fpcf/properties/string/StringMatcher.scala | 2 ++ 14 files changed, 44 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java index e4690e566d..72437d501b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constant.java @@ -5,6 +5,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @PropertyValidator(key = "StringConstancy", validator = ConstantStringMatcher.class) @Documented @Repeatable(Constants.class) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java index 092465146a..4be7497463 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Constants.java @@ -3,6 +3,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java index d52675a7a7..0b6330375f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/DomainLevel.java @@ -2,6 +2,7 @@ package org.opalj.fpcf.properties.string; /** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps * @author Maximilian Rüsch */ public enum DomainLevel { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java index 55f95e05f3..f6a422e9c6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamic.java @@ -5,6 +5,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @PropertyValidator(key = "StringConstancy", validator = DynamicStringMatcher.class) @Documented @Repeatable(Dynamics.class) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java index e4f1a0622b..ab0b34b717 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Dynamics.java @@ -3,6 +3,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java index 69da3017ed..505c8d3a65 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failure.java @@ -5,6 +5,9 @@ /** * Note that this annotation will be rewritten into {@link Invalid} or {@link Dynamic} depending on the soundness mode. + * + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch */ @Documented @Repeatable(Failures.class) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java index 6e0edf86a3..1a8c57bcbb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Failures.java @@ -3,6 +3,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java index c1321348d6..d19f597681 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalid.java @@ -5,6 +5,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @PropertyValidator(key = "StringConstancy", validator = InvalidStringMatcher.class) @Documented @Repeatable(Invalids.class) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java index fdcef6e2fb..a47be759ca 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Invalids.java @@ -3,6 +3,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java index dc448dc853..cec1133a6f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/Level.java @@ -2,6 +2,7 @@ package org.opalj.fpcf.properties.string; /** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps * @author Maximilian Rüsch */ public enum Level { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java index 3a455751db..53f9178533 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstant.java @@ -5,6 +5,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @PropertyValidator(key = "StringConstancy", validator = PartiallyConstantStringMatcher.class) @Documented @Repeatable(PartiallyConstants.class) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java index 4f7a2cdaeb..ca5f0233ff 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/PartiallyConstants.java @@ -3,6 +3,10 @@ import java.lang.annotation.*; +/** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps + * @author Maximilian Rüsch + */ @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java index 7f5cf4b2de..8494678f99 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string/SoundnessMode.java @@ -2,6 +2,7 @@ package org.opalj.fpcf.properties.string; /** + * @see org.opalj.fpcf.fixtures.string.SimpleStringOps * @author Maximilian Rüsch */ public enum SoundnessMode { diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala index fc33772727..c175662979 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string/StringMatcher.scala @@ -14,6 +14,8 @@ import org.opalj.br.fpcf.properties.string.StringConstancyLevel import org.opalj.br.fpcf.properties.string.StringConstancyProperty /** + * @see [[StringAnalysisTest]] + * @see [[org.opalj.fpcf.fixtures.string.SimpleStringOps]] * @author Maximilian Rüsch */ sealed trait StringMatcher extends AbstractPropertyMatcher { From 00ac79409541137d4990917bb9263db3c8a205b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 23:07:57 +0200 Subject: [PATCH 577/583] Add documentation to string analysis related properties --- .../br/fpcf/properties/SystemProperties.scala | 9 ++ .../string/StringConstancyLevel.scala | 5 + .../string/StringConstancyProperty.scala | 9 +- .../properties/string/StringTreeNode.scala | 150 ++++++++++++++---- .../fpcf/analyses/string/StringAnalysis.scala | 2 +- 5 files changed, 141 insertions(+), 34 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala index 7d68e15e99..d8ff07860b 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/SystemProperties.scala @@ -14,6 +14,15 @@ import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore /** + * Holds the possible values that a [[java.util.Properties]] can take on, e.g. by analyzing the parameters given to + * calls like [[java.util.Properties.setProperty]] that are found in reachable methods. + *

    + * Currently, values are not distinguished by the keys they are set for since the key parameters may also take on any + * value conforming to their string tree, which can be infinitely many (see [[StringTreeNode]]). + *

    + * All existing analyses do not distinguish between the system-wide properties (set through [[System.setProperty]] or + * similar) and all other [[java.util.Properties]] instances. + * * @author Maximilian Rüsch */ sealed trait SystemPropertiesPropertyMetaInformation extends PropertyMetaInformation { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index 63c0a2fcf4..5029116bc3 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -6,6 +6,11 @@ package properties package string /** + * Documents the string constancy that a string tree consisting of [[StringTreeNode]] has, meaning a summary whether the + * string tree in question is invalid, has at most constant values, has at most constant values concatenated with + * dynamic values or also contains un-concatenated dynamic values. The companion object also defines useful combination + * functions for instances of this trait. + * * @author Maximilian Rüsch */ sealed trait StringConstancyLevel diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala index 91a0b787bb..245b420310 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyProperty.scala @@ -13,6 +13,11 @@ import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore +/** + * Wrapper property around [[StringTreeNode]] to allow it to be stored in the [[PropertyStore]]. + * + * @author Maximilian Rüsch + */ sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { final type Self = StringConstancyProperty } @@ -61,12 +66,12 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta def apply(tree: StringTreeNode): StringConstancyProperty = new StringConstancyProperty(tree) /** - * @return Returns the lower bound from a lattice-point of view. + * @return The lower bound from a lattice-point of view. */ def lb: StringConstancyProperty = StringConstancyProperty(StringTreeNode.lb) /** - * @return Returns the upper bound from a lattice-point of view. + * @return The upper bound from a lattice-point of view. */ def ub: StringConstancyProperty = StringConstancyProperty(StringTreeNode.ub) } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 930a29814a..8c04f7596c 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -9,24 +9,50 @@ import scala.util.Try import scala.util.matching.Regex /** + * A single node that can be nested to create string trees that represent a set of possible string values. Its canonical + * reduction is a regex of all possible strings. + * + * @note This trait and all its implementations should be kept immutable to allow certain values to be cached. + * @see [[CachedHashCode]] [[CachedSimplifyNode]] + * * @author Maximilian Rüsch */ sealed trait StringTreeNode { val children: Seq[StringTreeNode] + /** + * The depth of the string tree measured by the count of nodes on the longest path from the root to a leaf. + */ lazy val depth: Int = children.map(_.depth).maxOption.getOrElse(0) + 1 - final def limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { - if (depth <= targetDepth) + + /** + * Replaces string tree nodes at the target depth if they have children. In case a [[SimpleStringTreeNode]] is given + * as a second parameter, this effectively limits the string tree to the given target depth. + * + * @param targetDepth The depth at which nodes should be replaced if they have children. + * @param replacement The replacement to set for nodes at the target depth if they have children. + * @return The modified tree if the target depth is smaller than the current depth or the same instance if it is not. + */ + final def replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + if (targetDepth >= depth) this else - _limitToDepth(targetDepth, replacement) + _replaceAtDepth(targetDepth, replacement) } - protected def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode + protected def _replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode + /** + * @return The string tree sorted with a stable ordering over its canonical reduction. + */ def sorted: StringTreeNode private var _regex: Option[String] = None + + /** + * @return The canonical reduction of the string tree, i.e. a regex representing the same set of string values as + * the tree itself. + */ final def toRegex: String = { if (_regex.isEmpty) { _regex = Some(_toRegex) @@ -34,13 +60,37 @@ sealed trait StringTreeNode { _regex.get } - def _toRegex: String - + protected def _toRegex: String + + /** + * Simplifies the string tree by e.g. flattening nested [[StringTreeOr]] instances. + * + * @return The simplified string tree or the same instance if nothing could be simplified. + * + * @see [[CachedSimplifyNode]] + */ def simplify: StringTreeNode + /** + * @return The constancy level of the string tree. + * + * @see [[StringConstancyLevel]] + */ def constancyLevel: StringConstancyLevel + /** + * The indices of any method parameter references using [[StringTreeParameter]] within the string tree. + */ lazy val parameterIndices: Set[Int] = children.flatMap(_.parameterIndices).toSet + + /** + * Replaces all [[StringTreeParameter]] instances in the string tree that represent a parameter index defined in the + * given map with the replacement value for that index. Keeps [[StringTreeParameter]] instances whose their index is + * not defined in the map. + * + * @param parameters A map from parameter indices to replacement values + * @return The modified string tree if something could be replaced or the same instance otherwise. + */ final def replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = { if (parameters.isEmpty || parameterIndices.isEmpty || @@ -52,10 +102,23 @@ sealed trait StringTreeNode { } protected def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode + /** + * @return True if this string tree node represents an empty string, false otherwise. + */ def isEmpty: Boolean = false + + /** + * @return True if this string tree node represents no string, false otherwise. + */ def isInvalid: Boolean = false } +object StringTreeNode { + + def lb: StringTreeNode = StringTreeDynamicString + def ub: StringTreeNode = StringTreeInvalidElement +} + sealed trait CachedSimplifyNode extends StringTreeNode { private var _simplified = false @@ -77,21 +140,17 @@ sealed trait CachedSimplifyNode extends StringTreeNode { sealed trait CachedHashCode extends Product { - // Performance optimizations private lazy val _hashCode = scala.util.hashing.MurmurHash3.productHash(this) override def hashCode(): Int = _hashCode override def canEqual(obj: Any): Boolean = obj.hashCode() == _hashCode } -object StringTreeNode { - - def lb: StringTreeNode = StringTreeDynamicString - def ub: StringTreeNode = StringTreeInvalidElement -} - +/** + * Represents the concatenation of all its children. + */ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends CachedSimplifyNode with CachedHashCode { - override def _toRegex: String = { + override protected def _toRegex: String = { children.size match { case 0 => throw new IllegalStateException("Tried to convert StringTreeConcat with no children to a regex!") case 1 => children.head.toRegex @@ -136,11 +195,11 @@ case class StringTreeConcat(override val children: Seq[StringTreeNode]) extends } } - def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + def _replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { if (targetDepth == 1) replacement else - StringTreeConcat(children.map(_.limitToDepth(targetDepth - 1, replacement))) + StringTreeConcat(children.map(_.replaceAtDepth(targetDepth - 1, replacement))) } } @@ -156,13 +215,16 @@ object StringTreeConcat { } } +/** + * Represents the free choice between all its children. + */ trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { protected val _children: Iterable[StringTreeNode] override final lazy val children: Seq[StringTreeNode] = _children.toSeq - override def _toRegex: String = { + override protected def _toRegex: String = { children.size match { case 0 => throw new IllegalStateException("Tried to convert StringTreeOr with no children to a regex!") case 1 => children.head.toRegex @@ -193,6 +255,12 @@ object StringTreeOr { def fromNodes(children: StringTreeNode*): StringTreeNode = SetBasedStringTreeOr.createWithSimplify(children.toSet) } +/** + * @inheritdoc + * + * Based on a [[Seq]] for children storage. To be used if the order of children is important for e.g. reduction to a + * regex and subsequent comparison to another string tree. + */ private case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNode]) extends StringTreeOr { override def sorted: StringTreeNode = SeqBasedStringTreeOr(children.map(_.sorted).sortBy(_.toRegex)) @@ -228,11 +296,11 @@ private case class SeqBasedStringTreeOr(override val _children: Seq[StringTreeNo } } - def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + def _replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { if (targetDepth == 1) replacement else - SeqBasedStringTreeOr(children.map(_.limitToDepth(targetDepth - 1, replacement))) + SeqBasedStringTreeOr(children.map(_.replaceAtDepth(targetDepth - 1, replacement))) } } @@ -249,6 +317,11 @@ object SeqBasedStringTreeOr { } } +/** + * @inheritdoc + * + * Based on a [[Set]] for children storage. To be used if the order of children is NOT important. + */ case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) extends StringTreeOr { override lazy val depth: Int = _children.map(_.depth).maxOption.getOrElse(0) + 1 @@ -272,11 +345,11 @@ case class SetBasedStringTreeOr(override val _children: Set[StringTreeNode]) ext } } - def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + def _replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { if (targetDepth == 1) replacement else - SetBasedStringTreeOr(_children.map(_.limitToDepth(targetDepth - 1, replacement))) + SetBasedStringTreeOr(_children.map(_.replaceAtDepth(targetDepth - 1, replacement))) } } @@ -315,6 +388,10 @@ object SetBasedStringTreeOr { } } +/** + * Represents a string tree leaf, i.e. a node having no children and can thus return itself during sorting and + * simplification. + */ sealed trait SimpleStringTreeNode extends StringTreeNode { override final val children: Seq[StringTreeNode] = Seq.empty @@ -323,16 +400,16 @@ sealed trait SimpleStringTreeNode extends StringTreeNode { override final def simplify: StringTreeNode = this override def _replaceParameters(parameters: Map[Int, StringTreeNode]): StringTreeNode = this - override def _limitToDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { + override def _replaceAtDepth(targetDepth: Int, replacement: StringTreeNode): StringTreeNode = { if (targetDepth == 1) replacement else - limitToDepth(targetDepth - 1, replacement) + replaceAtDepth(targetDepth - 1, replacement) } } case class StringTreeConst(string: String) extends SimpleStringTreeNode { - override def _toRegex: String = Regex.quoteReplacement(string).replaceAll("\\[", "\\\\[") + override protected def _toRegex: String = Regex.quoteReplacement(string).replaceAll("\\[", "\\\\[") override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant @@ -346,8 +423,14 @@ object StringTreeEmptyConst extends StringTreeConst("") { override def isEmpty: Boolean = true } +/** + * A placeholder for a method parameter value. Should be replaced using [[replaceParameters]] before reducing the string + * tree to a regex. + * + * @param index The method parameter index that is being represented. + */ case class StringTreeParameter(index: Int) extends SimpleStringTreeNode { - override def _toRegex: String = ".*" + override protected def _toRegex: String = ".*" override lazy val parameterIndices: Set[Int] = Set(index) @@ -369,7 +452,7 @@ object StringTreeParameter { } object StringTreeInvalidElement extends SimpleStringTreeNode { - override def _toRegex: String = throw new UnsupportedOperationException() + override protected def _toRegex: String = throw new UnsupportedOperationException() override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Invalid @@ -377,26 +460,31 @@ object StringTreeInvalidElement extends SimpleStringTreeNode { } object StringTreeNull extends SimpleStringTreeNode { - // Using this element nested in some other element might lead to unexpected results... - override def _toRegex: String = "^null$" + // IMPROVE Using this element nested in some other element might lead to unexpected results since it contains regex + // matching characters for the beginning and end of a string. + override protected def _toRegex: String = "^null$" override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Constant } object StringTreeDynamicString extends SimpleStringTreeNode { - override def _toRegex: String = ".*" + override protected def _toRegex: String = ".*" override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } object StringTreeDynamicInt extends SimpleStringTreeNode { - override def _toRegex: String = "^-?\\d+$" + // IMPROVE Using this element nested in some other element might lead to unexpected results since it contains regex + // matching characters for the beginning and end of a string. + override protected def _toRegex: String = "^-?\\d+$" override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } object StringTreeDynamicFloat extends SimpleStringTreeNode { - override def _toRegex: String = "^-?\\d*\\.{0,1}\\d+$" + // IMPROVE Using this element nested in some other element might lead to unexpected results since it contains regex + // matching characters for the beginning and end of a string. + override protected def _toRegex: String = "^-?\\d*\\.{0,1}\\d+$" override def constancyLevel: StringConstancyLevel = StringConstancyLevel.Dynamic } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 0e184f1847..525de1afe9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -174,7 +174,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec // recursing functions are properly handled using e.g. the widen-converge approach. state.hitDepthThreshold = true if (highSoundness) { - tree.limitToDepth(depthThreshold, StringTreeNode.lb) + tree.replaceAtDepth(depthThreshold, StringTreeNode.lb) } else { // In low soundness, we cannot decrease the matched string values by limiting the string tree // with the upper bound. We should also not limit it with the lower bound, since that would From 018369b7b42012c2af1cfc44b80a95e6b84a1a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 18 Sep 2024 23:18:14 +0200 Subject: [PATCH 578/583] Improve string constancy level tests --- .../string/StringConstancyLevel.scala | 2 +- .../br/string/StringConstancyLevelTests.scala | 73 +++++++++++++++++++ .../StringConstancyLevelTests.scala | 39 ---------- 3 files changed, 74 insertions(+), 40 deletions(-) create mode 100644 OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala delete mode 100644 OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index 5029116bc3..bf74921917 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -78,7 +78,7 @@ object StringConstancyLevel { level2: StringConstancyLevel ): StringConstancyLevel = { if (level1 == Invalid || level2 == Invalid) { - PartiallyConstant + Invalid } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { PartiallyConstant } else if ((level1 == Constant && level2 == Dynamic) || (level1 == Dynamic && level2 == Constant)) { diff --git a/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala b/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala new file mode 100644 index 0000000000..b7678b4737 --- /dev/null +++ b/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala @@ -0,0 +1,73 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package br +package string + +import org.scalatest.funsuite.AnyFunSuite + +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Constant +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Dynamic +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Invalid +import org.opalj.br.fpcf.properties.string.StringConstancyLevel.PartiallyConstant + +/** + * Tests for [[StringConstancyLevel]] methods. + * + * @author Maximilian Rüsch + */ +@org.junit.runner.RunWith(classOf[org.scalatestplus.junit.JUnitRunner]) +class StringConstancyLevelTests extends AnyFunSuite { + + test("tests that the more general string constancy level is computed correctly") { + // Trivial cases + assert(StringConstancyLevel.determineMoreGeneral(Invalid, Invalid) == Invalid) + assert(StringConstancyLevel.determineMoreGeneral(Constant, Constant) == Constant) + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Dynamic) == Dynamic) + + // <= Constant + assert(StringConstancyLevel.determineMoreGeneral(Constant, Invalid) == Constant) + assert(StringConstancyLevel.determineMoreGeneral(Invalid, Constant) == Constant) + + // <= PartiallyConstant + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Invalid) == PartiallyConstant) + assert(StringConstancyLevel.determineMoreGeneral(Invalid, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Constant) == PartiallyConstant) + assert(StringConstancyLevel.determineMoreGeneral(Constant, PartiallyConstant) == PartiallyConstant) + + // <= Dynamic + assert(StringConstancyLevel.determineMoreGeneral(Invalid, Dynamic) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Invalid) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(Constant, Dynamic) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Constant) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Dynamic) == Dynamic) + assert(StringConstancyLevel.determineMoreGeneral(Dynamic, PartiallyConstant) == Dynamic) + } + + test("tests that the string constancy level for concatenation of two levels is computed correctly") { + // Trivial cases + assert(StringConstancyLevel.determineForConcat(Invalid, Invalid) == Invalid) + assert(StringConstancyLevel.determineForConcat(Constant, Constant) == Constant) + assert(StringConstancyLevel.determineForConcat(PartiallyConstant, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.determineForConcat(Dynamic, Dynamic) == Dynamic) + + // Invalid blocks everything + assert(StringConstancyLevel.determineForConcat(Constant, Invalid) == Invalid) + assert(StringConstancyLevel.determineForConcat(Invalid, Constant) == Invalid) + assert(StringConstancyLevel.determineForConcat(PartiallyConstant, Invalid) == Invalid) + assert(StringConstancyLevel.determineForConcat(Invalid, PartiallyConstant) == Invalid) + assert(StringConstancyLevel.determineForConcat(Invalid, Dynamic) == Invalid) + assert(StringConstancyLevel.determineForConcat(Dynamic, Invalid) == Invalid) + + // PartiallyConstant can be retained + assert(StringConstancyLevel.determineForConcat(PartiallyConstant, Constant) == PartiallyConstant) + assert(StringConstancyLevel.determineForConcat(Constant, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.determineForConcat(PartiallyConstant, Dynamic) == PartiallyConstant) + assert(StringConstancyLevel.determineForConcat(Dynamic, PartiallyConstant) == PartiallyConstant) + + // PartiallyConstant can be constructed + assert(StringConstancyLevel.determineForConcat(Constant, Dynamic) == PartiallyConstant) + assert(StringConstancyLevel.determineForConcat(Dynamic, Constant) == PartiallyConstant) + } +} 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 deleted file mode 100644 index 8d55320cc6..0000000000 --- a/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package string_definition - -import org.scalatest.funsuite.AnyFunSuite - -import org.opalj.br.fpcf.properties.string.StringConstancyLevel -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Constant -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.Dynamic -import org.opalj.br.fpcf.properties.string.StringConstancyLevel.PartiallyConstant - -/** - * Tests for [[StringConstancyLevel]] methods. - * - * @author Maximilian Rüsch - */ -@org.junit.runner.RunWith(classOf[org.scalatestplus.junit.JUnitRunner]) -class StringConstancyLevelTests extends AnyFunSuite { - - test("tests that the more general string constancy level is computed correctly") { - // Trivial cases - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Dynamic) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, PartiallyConstant) == PartiallyConstant) - 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, PartiallyConstant } - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Dynamic) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, PartiallyConstant) == Dynamic) - - // { PartiallyConstant, Constant } - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Constant) == PartiallyConstant) - assert(StringConstancyLevel.determineMoreGeneral(Constant, PartiallyConstant) == PartiallyConstant) - } -} From 4c54426187438a9a04b77b894dfde0e245d4c0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Sep 2024 08:47:57 +0200 Subject: [PATCH 579/583] Remove high soundness type alias --- .../tac/fpcf/analyses/string/StringAnalysisState.scala | 2 +- .../tac/fpcf/analyses/string/StringInterpreter.scala | 10 +++++----- .../fpcf/analyses/string/UniversalStringConfig.scala | 2 +- .../string/flowanalysis/DataFlowAnalysis.scala | 2 +- .../flowanalysis/MethodStringFlowAnalysisState.scala | 2 +- .../l0/interpretation/BinaryExprInterpreter.scala | 2 +- .../l1/interpretation/L1FunctionCallInterpreter.scala | 2 +- .../L1NonVirtualFunctionCallInterpreter.scala | 2 +- .../L1NonVirtualMethodCallInterpreter.scala | 2 +- .../L1StaticFunctionCallInterpreter.scala | 2 +- .../L1VirtualFunctionCallInterpreter.scala | 8 ++++---- .../L1VirtualMethodCallInterpreter.scala | 2 +- .../L2VirtualFunctionCallInterpreter.scala | 2 +- .../l3/interpretation/L3FieldReadInterpreter.scala | 2 +- .../org/opalj/tac/fpcf/analyses/string/package.scala | 1 - 15 files changed, 21 insertions(+), 22 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 0088eb1124..3697d3a30f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -172,7 +172,7 @@ private[string] case class MethodParameterContextStringAnalysisState( def updateParamDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = _paramDependees(dependee.e) = dependee - def currentTreeUB(implicit highSoundness: HighSoundness): StringTreeNode = { + def currentTreeUB(implicit highSoundness: Boolean): StringTreeNode = { var paramOptions = _methodToEntityMapping.keys.toSeq .sortBy(_.id) .flatMap { dm => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 0121711dab..2c475e2aab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -28,10 +28,10 @@ trait StringInterpreter { */ def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult - def failureTree(implicit highSoundness: HighSoundness): StringTreeNode = + def failureTree(implicit highSoundness: Boolean): StringTreeNode = StringInterpreter.failureTree - def failure(v: PV)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = + def failure(v: PV)(implicit state: InterpretationState, highSoundness: Boolean): Result = StringInterpreter.failure(v) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = @@ -46,15 +46,15 @@ trait StringInterpreter { object StringInterpreter { - def failureTree(implicit highSoundness: HighSoundness): StringTreeNode = { + def failureTree(implicit highSoundness: Boolean): StringTreeNode = { if (highSoundness) StringTreeNode.lb else StringTreeNode.ub } - def failure(v: V)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = + def failure(v: V)(implicit state: InterpretationState, highSoundness: Boolean): Result = failure(v.toPersistentForm(state.tac.stmts)) - def failure(pv: PV)(implicit state: InterpretationState, highSoundness: HighSoundness): Result = + def failure(pv: PV)(implicit state: InterpretationState, highSoundness: Boolean): Result = computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, pv, failureTree)) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala index 87b6d98fba..3925c76f68 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala @@ -21,7 +21,7 @@ trait UniversalStringConfig { private final val ConfigLogCategory = "analysis configuration - string analysis - universal" - implicit val highSoundness: HighSoundness = { + implicit val highSoundness: Boolean = { val isHighSoundness = try { project.config.getBoolean(UniversalStringConfig.HighSoundnessConfigKey) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index 672fe3f64e..b9626c3c2d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -19,7 +19,7 @@ import scalax.collection.GraphTraversal.Parameters class DataFlowAnalysis( private val controlTree: ControlTree, private val superFlowGraph: SuperFlowGraph, - private val highSoundness: HighSoundness + private val highSoundness: Boolean ) { private val _nodeOrderings = mutable.Map.empty[FlowGraphNode, Seq[SuperFlowGraph#NodeT]] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala index 2e05f11ad8..abb63c944f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala @@ -72,7 +72,7 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var } private var _startEnv: StringTreeEnvironment = StringTreeEnvironment(Map.empty) - def getStartEnvAndReset(implicit highSoundness: HighSoundness): StringTreeEnvironment = { + def getStartEnvAndReset(implicit highSoundness: Boolean): StringTreeEnvironment = { if (pcToWebChangeMapping.exists(_._2)) { val webs = getWebs val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index 32b57acd4a..b06fd8210c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * @author Maximilian Rüsch */ case class BinaryExprInterpreter()( - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends AssignmentBasedStringInterpreter { override type E = BinaryExpr[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index aa56e02684..72b9d9325c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -35,7 +35,7 @@ trait L1FunctionCallInterpreter override type E <: FunctionCall[V] implicit val ps: PropertyStore - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean type CallState <: FunctionCallState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala index a9b4c9765e..63893d7ff7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualFunctionCallInterpreter.scala @@ -21,7 +21,7 @@ import org.opalj.tac.fpcf.properties.TACAI case class L1NonVirtualFunctionCallInterpreter()( implicit val p: SomeProject, implicit val ps: PropertyStore, - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends AssignmentLikeBasedStringInterpreter with L1FunctionCallInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 3ed25437db..50f8bcb271 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -18,7 +18,7 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment * @author Maximilian Rüsch */ case class L1NonVirtualMethodCallInterpreter()( - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends StringInterpreter { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 0da1b83174..4ae2dcf1dc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -25,7 +25,7 @@ case class L1StaticFunctionCallInterpreter()( override val p: SomeProject, override val ps: PropertyStore, override val project: SomeProject, - val highSoundness: HighSoundness + val highSoundness: Boolean ) extends AssignmentBasedStringInterpreter with L1ArbitraryStaticFunctionCallInterpreter with L1StringValueOfFunctionCallInterpreter diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index ca864a9fb3..e8dc7a237b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -32,7 +32,7 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ class L1VirtualFunctionCallInterpreter( - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends AssignmentLikeBasedStringInterpreter with L1ArbitraryVirtualFunctionCallInterpreter with L1AppendCallInterpreter @@ -101,7 +101,7 @@ class L1VirtualFunctionCallInterpreter( private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends AssignmentLikeBasedStringInterpreter { - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean protected def interpretArbitraryCall(target: PV, call: E)(implicit state: InterpretationState @@ -113,7 +113,7 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends Assignme */ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { - val highSoundness: HighSoundness + val highSoundness: Boolean override type E = VirtualFunctionCall[V] @@ -162,7 +162,7 @@ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStri override type E <: VirtualFunctionCall[V] - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean def interpretSubstringCall(at: Option[PV], pt: PV, call: E)(implicit state: InterpretationState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 6da2846b1e..9b4339fa44 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -19,7 +19,7 @@ import org.opalj.value.TheIntegerValue * @author Maximilian Rüsch */ case class L1VirtualMethodCallInterpreter()( - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends StringInterpreter { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala index f6e07a06e8..cbe37895a9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2VirtualFunctionCallInterpreter.scala @@ -38,7 +38,7 @@ class L2VirtualFunctionCallInterpreter( implicit val ps: PropertyStore, implicit val contextProvider: ContextProvider, implicit val project: SomeProject, - override implicit val highSoundness: HighSoundness + override implicit val highSoundness: Boolean ) extends L1VirtualFunctionCallInterpreter with StringInterpreter with L1SystemPropertiesInterpreter diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 698006b5a3..a86d584b25 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -43,7 +43,7 @@ class L3FieldReadInterpreter( implicit val project: SomeProject, implicit val declaredFields: DeclaredFields, implicit val contextProvider: ContextProvider, - implicit val highSoundness: HighSoundness + implicit val highSoundness: Boolean ) extends AssignmentBasedStringInterpreter { override type E = FieldRead[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala index 2e888ec8b7..d55bdc5ce6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/package.scala @@ -13,7 +13,6 @@ import org.opalj.br.fpcf.properties.Context */ package object string { - type HighSoundness = Boolean type TAC = TACode[TACMethodParameter, V] private[string] case class VariableDefinition(pc: Int, pv: PV, m: Method) From 10615d42395ee0387a7814de4c2b395db0eea4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Sep 2024 08:51:21 +0200 Subject: [PATCH 580/583] Rename string constancy level meet function --- .../string/StringConstancyLevel.scala | 10 ++---- .../properties/string/StringTreeNode.scala | 2 +- .../br/string/StringConstancyLevelTests.scala | 32 +++++++++---------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala index bf74921917..984d83bacf 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringConstancyLevel.scala @@ -45,10 +45,7 @@ object StringConstancyLevel { * @param level2 The second level. * @return Returns the more general level of both given inputs. */ - def determineMoreGeneral( - level1: StringConstancyLevel, - level2: StringConstancyLevel - ): StringConstancyLevel = { + def meet(level1: StringConstancyLevel, level2: StringConstancyLevel): StringConstancyLevel = { if (level1 == Dynamic || level2 == Dynamic) { Dynamic } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { @@ -73,10 +70,7 @@ object StringConstancyLevel { * @param level2 The second level. * @return Returns the level for a concatenation. */ - def determineForConcat( - level1: StringConstancyLevel, - level2: StringConstancyLevel - ): StringConstancyLevel = { + def determineForConcat(level1: StringConstancyLevel, level2: StringConstancyLevel): StringConstancyLevel = { if (level1 == Invalid || level2 == Invalid) { Invalid } else if (level1 == PartiallyConstant || level2 == PartiallyConstant) { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala index 8c04f7596c..2467f04cc3 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string/StringTreeNode.scala @@ -233,7 +233,7 @@ trait StringTreeOr extends CachedSimplifyNode with CachedHashCode { } override def constancyLevel: StringConstancyLevel = - _children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.determineMoreGeneral) + _children.map(_.constancyLevel).reduceLeft(StringConstancyLevel.meet) override lazy val parameterIndices: Set[Int] = _children.flatMap(_.parameterIndices).toSet } diff --git a/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala b/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala index b7678b4737..7bd9cb49f9 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/string/StringConstancyLevelTests.scala @@ -21,28 +21,28 @@ class StringConstancyLevelTests extends AnyFunSuite { test("tests that the more general string constancy level is computed correctly") { // Trivial cases - assert(StringConstancyLevel.determineMoreGeneral(Invalid, Invalid) == Invalid) - assert(StringConstancyLevel.determineMoreGeneral(Constant, Constant) == Constant) - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, PartiallyConstant) == PartiallyConstant) - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Dynamic) == Dynamic) + assert(StringConstancyLevel.meet(Invalid, Invalid) == Invalid) + assert(StringConstancyLevel.meet(Constant, Constant) == Constant) + assert(StringConstancyLevel.meet(PartiallyConstant, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.meet(Dynamic, Dynamic) == Dynamic) // <= Constant - assert(StringConstancyLevel.determineMoreGeneral(Constant, Invalid) == Constant) - assert(StringConstancyLevel.determineMoreGeneral(Invalid, Constant) == Constant) + assert(StringConstancyLevel.meet(Constant, Invalid) == Constant) + assert(StringConstancyLevel.meet(Invalid, Constant) == Constant) // <= PartiallyConstant - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Invalid) == PartiallyConstant) - assert(StringConstancyLevel.determineMoreGeneral(Invalid, PartiallyConstant) == PartiallyConstant) - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Constant) == PartiallyConstant) - assert(StringConstancyLevel.determineMoreGeneral(Constant, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.meet(PartiallyConstant, Invalid) == PartiallyConstant) + assert(StringConstancyLevel.meet(Invalid, PartiallyConstant) == PartiallyConstant) + assert(StringConstancyLevel.meet(PartiallyConstant, Constant) == PartiallyConstant) + assert(StringConstancyLevel.meet(Constant, PartiallyConstant) == PartiallyConstant) // <= Dynamic - assert(StringConstancyLevel.determineMoreGeneral(Invalid, Dynamic) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Invalid) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(Constant, Dynamic) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, Constant) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(PartiallyConstant, Dynamic) == Dynamic) - assert(StringConstancyLevel.determineMoreGeneral(Dynamic, PartiallyConstant) == Dynamic) + assert(StringConstancyLevel.meet(Invalid, Dynamic) == Dynamic) + assert(StringConstancyLevel.meet(Dynamic, Invalid) == Dynamic) + assert(StringConstancyLevel.meet(Constant, Dynamic) == Dynamic) + assert(StringConstancyLevel.meet(Dynamic, Constant) == Dynamic) + assert(StringConstancyLevel.meet(PartiallyConstant, Dynamic) == Dynamic) + assert(StringConstancyLevel.meet(Dynamic, PartiallyConstant) == Dynamic) } test("tests that the string constancy level for concatenation of two levels is computed correctly") { From 8d8713b47185e900640ec593ef8fa09188a9956d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Sep 2024 08:53:49 +0200 Subject: [PATCH 581/583] Rename config object --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 4 ++-- .../opalj/tac/fpcf/analyses/string/StringAnalysis.scala | 2 +- ...ersalStringConfig.scala => StringAnalysisConfig.scala} | 8 ++++---- .../string/flowanalysis/MethodStringFlowAnalysis.scala | 2 +- .../string/interpretation/InterpretationHandler.scala | 2 +- .../analyses/string/trivial/TrivialStringAnalysis.scala | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/{UniversalStringConfig.scala => StringAnalysisConfig.scala} (83%) 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 10017953d3..8c04193921 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 @@ -52,7 +52,7 @@ import org.opalj.tac.cg.CallGraphKey import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.string.StringAnalysis -import org.opalj.tac.fpcf.analyses.string.UniversalStringConfig +import org.opalj.tac.fpcf.analyses.string.StringAnalysisConfig import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringAnalysis @@ -83,7 +83,7 @@ sealed abstract class StringAnalysisTest extends PropertiesTest { } super.createConfig() - .withValue(UniversalStringConfig.HighSoundnessConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) + .withValue(StringAnalysisConfig.HighSoundnessConfigKey, ConfigValueFactory.fromAnyRef(highSoundness)) .withValue(StringAnalysis.DepthThresholdConfigKey, ConfigValueFactory.fromAnyRef(10)) .withValue( MethodStringFlowAnalysis.ExcludedPackagesConfigKey, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 525de1afe9..b6fd8a9350 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -37,7 +37,7 @@ import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow -trait StringAnalysis extends FPCFAnalysis with UniversalStringConfig { +trait StringAnalysis extends FPCFAnalysis with StringAnalysisConfig { private final val ConfigLogCategory = "analysis configuration - string analysis" diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala similarity index 83% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala index 3925c76f68..cf9e241015 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/UniversalStringConfig.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala @@ -14,7 +14,7 @@ import org.opalj.log.OPALLogger.logOnce /** * @author Maximilian Rüsch */ -trait UniversalStringConfig { +trait StringAnalysisConfig { val project: SomeProject implicit def logContext: LogContext @@ -24,11 +24,11 @@ trait UniversalStringConfig { implicit val highSoundness: Boolean = { val isHighSoundness = try { - project.config.getBoolean(UniversalStringConfig.HighSoundnessConfigKey) + project.config.getBoolean(StringAnalysisConfig.HighSoundnessConfigKey) } catch { case t: Throwable => logOnce { - Error(ConfigLogCategory, s"couldn't read: ${UniversalStringConfig.HighSoundnessConfigKey}", t) + Error(ConfigLogCategory, s"couldn't read: ${StringAnalysisConfig.HighSoundnessConfigKey}", t) } false } @@ -38,7 +38,7 @@ trait UniversalStringConfig { } } -object UniversalStringConfig { +object StringAnalysisConfig { final val HighSoundnessConfigKey = "org.opalj.fpcf.analyses.string.highSoundness" } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index 4c648b12c2..891bdfc1e0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -31,7 +31,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @author Maximilian Rüsch */ -class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis with UniversalStringConfig { +class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis with StringAnalysisConfig { private final val ConfigLogCategory = "analysis configuration - method string flow analysis" diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 7c1805e62e..80b70954d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -24,7 +24,7 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty * * @author Maximilian Rüsch */ -abstract class InterpretationHandler extends FPCFAnalysis with UniversalStringConfig { +abstract class InterpretationHandler extends FPCFAnalysis with StringAnalysisConfig { def analyze(entity: MethodPC): ProperPropertyComputationResult = { val tacaiEOptP = ps(entity.dm.definedMethod, TACAI.key) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index b3ef60a661..91112f8642 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -39,7 +39,7 @@ import org.opalj.tac.fpcf.properties.TACAI * * @see [[StringAnalysis]] */ -class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis with UniversalStringConfig { +class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis with StringAnalysisConfig { private case class TrivialStringAnalysisState(entity: VariableContext, var tacDependee: EOptionP[Method, TACAI]) From db2daf09d1576fe9ed2ec755e5dad1ab0796eec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Sep 2024 08:55:12 +0200 Subject: [PATCH 582/583] Improve equality check for supported types --- .../string/l3/interpretation/L3FieldReadInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index a86d584b25..034cea4504 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -215,5 +215,5 @@ object L3FieldReadInterpreter { * Checks whether the given type is supported by the field read analysis, i.e. if it may contain values desirable * AND resolvable by the string analysis as a whole. */ - private def isSupportedType(fieldType: FieldType): Boolean = fieldType.isBaseType || fieldType == ObjectType.String + private def isSupportedType(fieldType: FieldType): Boolean = fieldType.isBaseType || (fieldType eq ObjectType.String) } From 07d9c3faab5b5ac87f7a212cef74d21e40d2fe8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Sep 2024 23:17:14 +0200 Subject: [PATCH 583/583] Improve documentation for all string analysis modules --- .../info/StringAnalysisReflectiveCalls.scala | 2 +- OPAL/tac/src/main/resources/reference.conf | 29 +++- .../src/main/scala/org/opalj/tac/PDUWeb.scala | 22 +-- .../fpcf/analyses/string/StringAnalysis.scala | 38 ++++- .../string/StringAnalysisConfig.scala | 2 + .../string/StringAnalysisScheduler.scala | 75 ++------- .../analyses/string/StringAnalysisState.scala | 31 +++- .../analyses/string/StringInterpreter.scala | 41 ++++- .../flowanalysis/DataFlowAnalysis.scala | 34 +++- .../string/flowanalysis/FlowGraphNode.scala | 38 ++++- .../MethodStringFlowAnalysis.scala | 20 ++- .../MethodStringFlowAnalysisScheduler.scala | 58 +++++++ .../MethodStringFlowAnalysisState.scala | 12 +- .../flowanalysis/StructuralAnalysis.scala | 147 ++++++++++++++---- .../string/flowanalysis/package.scala | 19 ++- .../InterpretationHandler.scala | 11 +- .../interpretation/InterpretationState.scala | 10 ++ .../StringFlowAnalysisScheduler.scala | 54 +++++++ .../analyses/string/l0/L0StringAnalysis.scala | 3 + .../BinaryExprInterpreter.scala | 4 + .../L0InterpretationHandler.scala | 5 +- .../SimpleValueConstExprInterpreter.scala | 7 +- .../analyses/string/l1/L1StringAnalysis.scala | 3 + .../L1FunctionCallInterpreter.scala | 6 + .../L1InterpretationHandler.scala | 5 +- .../L1NonVirtualMethodCallInterpreter.scala | 3 + .../L1StaticFunctionCallInterpreter.scala | 6 + .../L1SystemPropertiesInterpreter.scala | 5 + .../L1VirtualFunctionCallInterpreter.scala | 10 +- .../L1VirtualMethodCallInterpreter.scala | 3 + .../analyses/string/l2/L2StringAnalysis.scala | 3 + .../L2InterpretationHandler.scala | 7 +- .../analyses/string/l3/L3StringAnalysis.scala | 3 + .../L3FieldReadInterpreter.scala | 4 +- .../L3InterpretationHandler.scala | 5 +- .../trivial/TrivialStringAnalysis.scala | 8 +- .../SystemPropertiesAnalysis.scala | 17 +- .../SystemPropertiesState.scala | 22 +-- .../properties/string/MethodStringFlow.scala | 5 + .../string/StringFlowFunction.scala | 4 + .../string/StringTreeEnvironment.scala | 3 + 41 files changed, 598 insertions(+), 186 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisScheduler.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/StringFlowAnalysisScheduler.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 index fd65f53ef0..4121a0d735 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 @@ -43,9 +43,9 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis import org.opalj.tac.fpcf.analyses.fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysisScheduler -import org.opalj.tac.fpcf.analyses.string.LazyMethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.LazyStringAnalysis import org.opalj.tac.fpcf.analyses.string.VariableContext +import org.opalj.tac.fpcf.analyses.string.flowanalysis.LazyMethodStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringFlowAnalysis diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 379abb0515..3f4707d64f 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -112,9 +112,34 @@ org.opalj { description = "Produces points-to sets for the results of reflection methods like Class.getMethod.", eagerFactory = "org.opalj.tac.fpcf.analyses.pointsto.ReflectionAllocationsAnalysisScheduler" }, + "StringAnalysis" { + description = "Computes strings available on individual variables in the context of the method they are defined in.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.LazyStringAnalysis" + }, + "MethodStringFlowAnalysis" { + description = "Computes results of data flow analysis for string variables in the context of a method.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis" + }, + "L0StringFlowAnalysis" { + description = "Computes string flow functions on level L0 for individual statements in the context of a method to be used in data flow analysis.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.l0.LazyL0StringFlowAnalysis" + }, + "L1StringFlowAnalysis" { + description = "Computes string flow functions on level L1 for individual statements in the context of a method to be used in data flow analysis.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.l1.LazyL1StringFlowAnalysis" + }, + "L2StringFlowAnalysis" { + description = "Computes string flow functions on level L2 for individual statements in the context of a method to be used in data flow analysis.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.l2.LazyL2StringFlowAnalysis", + default = true + }, + "L3StringFlowAnalysis" { + description = "Computes string flow functions on level L3 for individual statements in the context of a method to be used in data flow analysis.", + lazyFactory = "org.opalj.tac.fpcf.analyses.string.l3.LazyL3StringFlowAnalysis" + }, "SystemPropertiesAnalysis" { - description = "Computes Strings available from system properties.", - triggeredFactory = "org.opalj.tac.fpcf.analyses.SystemPropertiesAnalysisScheduler" + description = "Computes strings available from all instances of the Properties class.", + triggeredFactory = "org.opalj.tac.fpcf.analyses.systemproperties.TriggeredSystemPropertiesAnalysisScheduler" } } }, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala index 8620c8db23..12b638b9f8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/PDUWeb.scala @@ -5,8 +5,16 @@ package tac import org.opalj.br.PDVar import org.opalj.br.PUVar import org.opalj.collection.immutable.IntTrieSet -import org.opalj.value.ValueInformation +/** + * Identifies a variable inside a given fixed method. A methods webs can be constructed through the maximal unions of + * all intersecting DU-UD-chains of the method. + * + * @param defPCs The def PCs of the variable that is identified through this web. + * @param usePCs The use PCs of the variable that is identified through this web. + * + * @author Maximilian Rüsch + */ case class PDUWeb( defPCs: IntTrieSet, usePCs: IntTrieSet @@ -15,8 +23,6 @@ case class PDUWeb( def combine(other: PDUWeb): PDUWeb = PDUWeb(other.defPCs ++ defPCs, other.usePCs ++ usePCs) - def size: Int = defPCs.size + usePCs.size - // Performance optimizations private lazy val _hashCode = scala.util.hashing.MurmurHash3.productHash(this) override def hashCode(): Int = _hashCode @@ -26,13 +32,7 @@ case class PDUWeb( object PDUWeb { def apply(pc: Int, pv: PV): PDUWeb = pv match { - case pdVar: PDVar[_] => forDVar(pc, pdVar) - case puVar: PUVar[_] => forUVar(pc, puVar) + case pdVar: PDVar[_] => PDUWeb(IntTrieSet(pc), pdVar.usePCs) + case puVar: PUVar[_] => PDUWeb(puVar.defPCs, IntTrieSet(pc)) } - - def forDVar[Value <: ValueInformation](defPC: Int, pdVar: PDVar[Value]): PDUWeb = - PDUWeb(IntTrieSet(defPC), pdVar.usePCs) - - def forUVar[Value <: ValueInformation](usePC: Int, puVar: PUVar[Value]): PDUWeb = - PDUWeb(puVar.defPCs, IntTrieSet(usePC)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index b6fd8a9350..cc4b1ba544 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -37,6 +37,11 @@ import org.opalj.log.OPALLogger.logOnce import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow +/** + * Base trait for all string analyses that compute results for the FPCF [[StringConstancyProperty]]. + * + * @author Maximilian Rüsch + */ trait StringAnalysis extends FPCFAnalysis with StringAnalysisConfig { private final val ConfigLogCategory = "analysis configuration - string analysis" @@ -51,7 +56,7 @@ trait StringAnalysis extends FPCFAnalysis with StringAnalysisConfig { } catch { case t: Throwable => logOnce(Error(ConfigLogCategory, s"couldn't read: ${StringAnalysis.DepthThresholdConfigKey}", t)) - 30 + 10 } logOnce(Info(ConfigLogCategory, "using depth threshold " + depthThreshold)) @@ -69,6 +74,16 @@ object StringAnalysis { } /** + * Analyzes a given variable in context of its method in a context free manner, i.e. without trying to resolve method + * parameter references present in the string tree computed for the variable. + * + * @note This particular instance of the analysis also tries to resolve string constants without involving the + * [[MethodStringFlow]] FPCF property in an effort to maintain scalability. Once a variable is known to require + * flow-sensitive information, a [[MethodStringFlow]] property is requested from the property store and all + * subsequent computations are made simply in reaction to changes in the method string flow dependee. + * + * @see [[org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis]], [[ContextStringAnalysis]] + * * @author Maximilian Rüsch */ private[string] class ContextFreeStringAnalysis(override val project: SomeProject) extends StringAnalysis { @@ -192,6 +207,12 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec } /** + * Analyzes a given variable in context of its method in a context-sensitive manner, i.e. resolving all method + * parameter references given in the string tree that was resolved for the variable from the + * [[ContextFreeStringAnalysis]]. + * + * @see [[ContextFreeStringAnalysis]], [[MethodParameterContextStringAnalysis]] + * * @author Maximilian Rüsch */ class ContextStringAnalysis(override val project: SomeProject) extends StringAnalysis { @@ -254,6 +275,15 @@ class ContextStringAnalysis(override val project: SomeProject) extends StringAna } } +/** + * Analyzes the possible values of a given method parameter by analyzing the actual method parameters at all call sites + * of the given method using the [[ContextStringAnalysis]]. Uses the call graph to find all call sites of the given + * method. + * + * @see [[ContextStringAnalysis]] + * + * @author Maximilian Rüsch + */ private[string] class MethodParameterContextStringAnalysis(override val project: SomeProject) extends StringAnalysis { private implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) @@ -276,14 +306,14 @@ private[string] class MethodParameterContextStringAnalysis(override val project: implicit val _state: MethodParameterContextStringAnalysisState = state eps match { case UBP(callers: Callers) => - val oldCallers = if (state._callersDependee.get.hasUBP) state._callersDependee.get.ub else NoCallers + val oldCallers = if (state.callersDependee.get.hasUBP) state.callersDependee.get.ub else NoCallers state.updateCallers(eps.asInstanceOf[EOptionP[DeclaredMethod, Callers]]) handleNewCallers(oldCallers, callers) computeResults case EUBP(m: Method, tacai: TACAI) => if (tacai.tac.isEmpty) { - state._discoveredUnknownTAC = true + state.discoveredUnknownTAC = true } else { state.getCallerContexts(m).foreach(handleTACForContext(tacai.tac.get, _)) } @@ -315,7 +345,7 @@ private[string] class MethodParameterContextStringAnalysis(override val project: if (tacEOptP.hasUBP) { val tacOpt = tacEOptP.ub.tac if (tacOpt.isEmpty) { - state._discoveredUnknownTAC = true + state.discoveredUnknownTAC = true } else { handleTACForContext(tacOpt.get, callerContext) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala index cf9e241015..8809d06b75 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisConfig.scala @@ -12,6 +12,8 @@ import org.opalj.log.LogContext import org.opalj.log.OPALLogger.logOnce /** + * Shared config between the multiple analyses of the string analysis package. + * * @author Maximilian Rüsch */ trait StringAnalysisConfig { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala index e638f4590c..05017ab7a0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisScheduler.scala @@ -5,7 +5,6 @@ package fpcf package analyses package string -import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.ContextProviderKey @@ -16,13 +15,12 @@ import org.opalj.br.fpcf.properties.string.StringConstancyProperty import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore -import org.opalj.tac.fpcf.analyses.string.flowanalysis.MethodStringFlowAnalysis -import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow -import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** + * A trait for FPCF analysis schedulers that combine the parts of the string analysis that produce + * [[StringConstancyProperty]]s. + * * @author Maximilian Rüsch */ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { @@ -47,6 +45,12 @@ sealed trait StringAnalysisScheduler extends FPCFAnalysisScheduler { override def afterPhaseCompletion(p: SomeProject, ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} } +/** + * A lazy adaptation of the [[StringAnalysisScheduler]]. All three string analyses are combined since the property store + * does not allow registering more than one lazy analysis scheduler for a given property. + * + * @author Maximilian Rüsch + */ object LazyStringAnalysis extends StringAnalysisScheduler with FPCFLazyAnalysisScheduler { @@ -69,64 +73,3 @@ object LazyStringAnalysis override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey) } - -sealed trait MethodStringFlowAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.ub(MethodStringFlow) - - override def uses: Set[PropertyBounds] = Set( - PropertyBounds.ub(TACAI), - PropertyBounds.ub(StringFlowFunctionProperty) - ) - - override final type InitializationData = MethodStringFlowAnalysis - override def init(p: SomeProject, ps: PropertyStore): InitializationData = new MethodStringFlowAnalysis(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 = {} -} - -object LazyMethodStringFlowAnalysis - extends MethodStringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(MethodStringFlow.key, initData.analyze) - initData - } - - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) -} - -sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { - - final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunctionProperty) - - override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) - - override final type InitializationData = InterpretationHandler - - 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 = {} -} - -trait LazyStringFlowAnalysis - extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { - - override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { - ps.registerLazyPropertyComputation(StringFlowFunctionProperty.key, initData.analyze) - - initData - } - - override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala index 3697d3a30f..e6eac2b89d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysisState.scala @@ -23,6 +23,13 @@ import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.string.MethodStringFlow +/** + * FPCF analysis state for the [[ContextFreeStringAnalysis]]. + * + * @see [[ContextFreeStringAnalysis]] + * + * @author Maximilian Rüsch + */ private[string] case class ContextFreeStringAnalysisState( entity: VariableDefinition, var tacaiDependee: EOptionP[Method, TACAI], @@ -36,6 +43,14 @@ private[string] case class ContextFreeStringAnalysisState( Set(tacaiDependee).filter(_.isRefinable) ++ stringFlowDependee.filter(_.isRefinable) } +/** + * FPCF analysis state for the [[ContextStringAnalysis]]. Provides support for adding required parameter dependees due + * to changes in the string dependee at runtime. + * + * @see [[ContextStringAnalysis]] + * + * @author Maximilian Rüsch + */ private[string] class ContextStringAnalysisState private ( val entity: VariableContext, private var _stringDependee: EOptionP[VariableDefinition, StringConstancyProperty], @@ -104,6 +119,14 @@ object ContextStringAnalysisState { } } +/** + * FPCF analysis state for the [[MethodParameterContextStringAnalysis]]. Provides support for finding call sites for a + * given method as well as the relevant TACAI properties and their call parameter string dependees. + * + * @see [[MethodParameterContextStringAnalysis]] + * + * @author Maximilian Rüsch + */ private[string] case class MethodParameterContextStringAnalysisState( entity: MethodParameterContext ) { @@ -113,10 +136,12 @@ private[string] case class MethodParameterContextStringAnalysisState( // Callers private[string] type CallerContext = (Context, Int) - var _callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = None + private var _callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = None private val _callerContexts: mutable.Map[CallerContext, Option[Call[V]]] = mutable.Map.empty private val _callerContextsByMethod: mutable.Map[Method, Seq[CallerContext]] = mutable.Map.empty + def callersDependee: Option[EOptionP[DeclaredMethod, Callers]] = _callersDependee + def updateCallers(newCallers: EOptionP[DeclaredMethod, Callers]): Unit = _callersDependee = Some(newCallers) def addCallerContext(callerContext: CallerContext): Unit = { @@ -132,7 +157,7 @@ private[string] case class MethodParameterContextStringAnalysisState( // TACAI private val _tacaiDependees: mutable.Map[Method, EOptionP[Method, TACAI]] = mutable.Map.empty - var _discoveredUnknownTAC: Boolean = false + var discoveredUnknownTAC: Boolean = false def updateTacaiDependee(tacEOptP: EOptionP[Method, TACAI]): Unit = _tacaiDependees(tacEOptP.e) = tacEOptP def getTacaiForContext(callerContext: CallerContext)(implicit ps: PropertyStore): EOptionP[Method, TACAI] = { @@ -183,7 +208,7 @@ private[string] case class MethodParameterContextStringAnalysisState( } if (highSoundness && ( - _discoveredUnknownTAC || + discoveredUnknownTAC || _callersDependee.exists(cd => cd.hasUBP && cd.ub.hasCallersWithUnknownContext) ) ) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala index 2c475e2aab..5ab76d3eec 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringInterpreter.scala @@ -15,6 +15,9 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** + * The base trait for all string interpreters, producing a FPCF [[StringFlowFunctionProperty]] for a given statement + * in the context of its method. + * * @author Maximilian Rüsch */ trait StringInterpreter { @@ -24,23 +27,27 @@ trait StringInterpreter { /** * @param instr The instruction that is to be interpreted. * @return A [[ProperPropertyComputationResult]] for the given pc containing the interpretation of the given - * instruction. + * instruction in the form of a [[StringFlowFunctionProperty]]. */ def interpret(instr: T)(implicit state: InterpretationState): ProperPropertyComputationResult - def failureTree(implicit highSoundness: Boolean): StringTreeNode = + protected[this] def failureTree(implicit highSoundness: Boolean): StringTreeNode = StringInterpreter.failureTree - def failure(v: PV)(implicit state: InterpretationState, highSoundness: Boolean): Result = + protected[this] def failure(v: PV)(implicit state: InterpretationState, highSoundness: Boolean): Result = StringInterpreter.failure(v) - def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = + protected[this] def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit + state: InterpretationState + ): Result = StringInterpreter.computeFinalResult(web, sff) - def computeFinalResult(webs: Set[PDUWeb], sff: StringFlowFunction)(implicit state: InterpretationState): Result = + protected[this] def computeFinalResult(webs: Set[PDUWeb], sff: StringFlowFunction)(implicit + state: InterpretationState + ): Result = StringInterpreter.computeFinalResult(webs, sff) - def computeFinalResult(p: StringFlowFunctionProperty)(implicit state: InterpretationState): Result = + protected[this] def computeFinalResult(p: StringFlowFunctionProperty)(implicit state: InterpretationState): Result = StringInterpreter.computeFinalResult(p) } @@ -58,15 +65,21 @@ object StringInterpreter { computeFinalResult(StringFlowFunctionProperty.constForVariableAt(state.pc, pv, failureTree)) def computeFinalResult(web: PDUWeb, sff: StringFlowFunction)(implicit state: InterpretationState): Result = - Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(web, sff))) + computeFinalResult(StringFlowFunctionProperty(web, sff)) def computeFinalResult(webs: Set[PDUWeb], sff: StringFlowFunction)(implicit state: InterpretationState): Result = - Result(FinalEP(InterpretationHandler.getEntity(state), StringFlowFunctionProperty(webs, sff))) + computeFinalResult(StringFlowFunctionProperty(webs, sff)) def computeFinalResult(p: StringFlowFunctionProperty)(implicit state: InterpretationState): Result = Result(FinalEP(InterpretationHandler.getEntity(state), p)) } +/** + * Base trait for all [[StringInterpreter]]s that have to evaluate parameters at a given call site, thus providing + * appropriate utility. + * + * @author Maximilian Rüsch + */ trait ParameterEvaluatingStringInterpreter extends StringInterpreter { protected def getParametersForPC(pc: Int)(implicit state: InterpretationState): Seq[Expr[V]] = { @@ -78,6 +91,12 @@ trait ParameterEvaluatingStringInterpreter extends StringInterpreter { } } +/** + * Base trait for all string interpreters that only process [[AssignmentLikeStmt]]s, allowing the trait to pre-unpack + * the expression of the [[AssignmentLikeStmt]]. + * + * @author Maximilian Rüsch + */ trait AssignmentLikeBasedStringInterpreter extends StringInterpreter { type E <: Expr[V] @@ -90,6 +109,12 @@ trait AssignmentLikeBasedStringInterpreter extends StringInterpreter { def interpretExpr(instr: T, expr: E)(implicit state: InterpretationState): ProperPropertyComputationResult } +/** + * Base trait for all string interpreters that only process [[Assignment]]s, allowing the trait to pre-unpack the + * assignment target variable as well as the operation performed by [[AssignmentLikeBasedStringInterpreter]]. + * + * @author Maximilian Rüsch + */ trait AssignmentBasedStringInterpreter extends AssignmentLikeBasedStringInterpreter { override type T = Assignment[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala index b9626c3c2d..1505634cab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/DataFlowAnalysis.scala @@ -16,6 +16,24 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import scalax.collection.GraphTraversal.BreadthFirst import scalax.collection.GraphTraversal.Parameters +/** + * Performs structural string data flow analysis based on the results of a [[StructuralAnalysis]]. In more detail, this + * means that the control tree produced by the [[StructuralAnalysis]] is traversed recursively in a depth-first manner. + * Individual regions are processed by piping a [[StringTreeEnvironment]] through their nodes and joining the + * environments where paths meet up. Thus, the individual flow functions defined at the statement PCs of a method are + * combined using region-type-specific patterns to effectively act as a string flow function of the entire region, which + * is then processed itself due to the recursive nature of the algorithm. + * + * @param controlTree The control tree from the structural analysis. + * @param superFlowGraph The super flow graph from the structural analysis + * @param highSoundness Whether to use high soundness mode or not. Currently, this influences the handling of loops, + * i.e. whether they are approximated by one execution of the loop body (low soundness) or via + * "any string" on all variables in the method (high soundness). + * + * @see [[StructuralAnalysis]], [[StringTreeEnvironment]] + * + * @author Maximilian Rüsch + */ class DataFlowAnalysis( private val controlTree: ControlTree, private val superFlowGraph: SuperFlowGraph, @@ -25,6 +43,14 @@ class DataFlowAnalysis( private val _nodeOrderings = mutable.Map.empty[FlowGraphNode, Seq[SuperFlowGraph#NodeT]] private val _removedBackEdgesGraphs = mutable.Map.empty[FlowGraphNode, (Boolean, SuperFlowGraph)] + /** + * Computes the resulting string tree environment after the data flow analysis. + * + * @param flowFunctionByPc A mapping from PC to the flow functions to be used + * @param startEnv The base environment which is piped into the first region to be processed. + * @return The resulting [[StringTreeEnvironment]] after execution of all string flow functions using the region + * hierarchy given in the control tree. + */ def compute( flowFunctionByPc: Map[Int, StringFlowFunction] )(startEnv: StringTreeEnvironment): StringTreeEnvironment = { @@ -37,10 +63,6 @@ class DataFlowAnalysis( pipeThroughNode(flowFunctionByPc)(startNode, startEnv) } - /** - * @note This function should be stable with regards to an ordering on the piped flow graph nodes, e.g. a proper - * region should always be traversed in the same way. - */ private def pipeThroughNode(flowFunctionByPc: Map[Int, StringFlowFunction])( node: FlowGraphNode, env: StringTreeEnvironment @@ -127,6 +149,7 @@ class DataFlowAnalysis( def processSelfLoop(entry: FlowGraphNode): StringTreeEnvironment = { val resultEnv = pipe(entry, env) + // IMPROVE only update affected variables instead of all if (resultEnv != env && highSoundness) env.updateAll(StringTreeDynamicString) else resultEnv } @@ -146,6 +169,7 @@ class DataFlowAnalysis( resultEnv = pipe(currentNode.outer, resultEnv) } + // IMPROVE only update affected variables instead of all if (resultEnv != envAfterEntry && highSoundness) envAfterEntry.updateAll(StringTreeDynamicString) else resultEnv } @@ -165,6 +189,7 @@ class DataFlowAnalysis( ) if (isCyclic) { + // IMPROVE only update affected variables instead of all if (highSoundness) env.updateAll(StringTreeDynamicString) else env.updateAll(StringTreeInvalidElement) } else { @@ -174,6 +199,7 @@ class DataFlowAnalysis( removedBackEdgesGraph.nodes.toSet, entry ) + // IMPROVE only update affected variables instead of all if (resultEnv != env && highSoundness) env.updateAll(StringTreeDynamicString) else resultEnv } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala index c2110e86ae..88f4cea03c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/FlowGraphNode.scala @@ -21,12 +21,26 @@ case object WhileLoop extends CyclicRegionType case object NaturalLoop extends CyclicRegionType case object Improper extends CyclicRegionType +/** + * A node in a flow graph and control tree produced by the [[StructuralAnalysis]]. + * + * @author Maximilian Rüsch + */ sealed trait FlowGraphNode extends Ordered[FlowGraphNode] { + def nodeIds: Set[Int] override def compare(that: FlowGraphNode): Int = nodeIds.toList.min.compare(that.nodeIds.toList.min) } +/** + * Represents a region of nodes in a [[FlowGraph]], consisting of multiple sub-nodes. Can identify general acyclic and + * cyclic structures or more specialised instances of such structures such as [[IfThenElse]] or [[WhileLoop]]. + * + * @param regionType The type of the region. + * @param nodeIds The union of all ids the leafs that are contained in this region. + * @param entry The direct child of this region that contains the first leaf to be executed when entering the region. + */ case class Region(regionType: RegionType, override val nodeIds: Set[Int], entry: FlowGraphNode) extends FlowGraphNode { override def toString: String = @@ -38,20 +52,34 @@ case class Region(regionType: RegionType, override val nodeIds: Set[Int], entry: override def canEqual(obj: Any): Boolean = obj.hashCode() == _hashCode } -case class Statement(nodeId: Int) extends FlowGraphNode { - override val nodeIds: Set[Int] = Set(nodeId) +/** + * Represents a single statement in a methods [[FlowGraph]] and is one of the leaf nodes to be grouped by a [[Region]]. + * + * @param pc The PC that the statement is given at. + */ +case class Statement(pc: Int) extends FlowGraphNode { + + override val nodeIds: Set[Int] = Set(pc) - override def toString: String = s"Statement($nodeId)" + override def toString: String = s"Statement($pc)" } +/** + * An additional global entry node to a methods [[FlowGraph]] to ensure only one entry node exists. + */ object GlobalEntry extends FlowGraphNode { + override val nodeIds: Set[Int] = Set(Int.MinValue + 1) - override def toString: String = s"GlobalEntry" + override def toString: String = "GlobalEntry" } +/** + * An additional global exit node to a methods [[FlowGraph]] to ensure only one exit node exists. + */ object GlobalExit extends FlowGraphNode { + override val nodeIds: Set[Int] = Set(Int.MinValue) - override def toString: String = s"GlobalExit" + override def toString: String = "GlobalExit" } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala index 891bdfc1e0..d35e367220 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysis.scala @@ -29,6 +29,15 @@ import org.opalj.tac.fpcf.properties.string.MethodStringFlow import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** + * Analyzes a methods string flow results by applying a [[StructuralAnalysis]] to identify all control flow regions of + * the methods CFG and subsequently applying a [[DataFlowAnalysis]] to compute a resulting string tree environment + * using string flow functions derived from the FPCF [[StringFlowFunctionProperty]]. + * + * @note Packages can be configured to be excluded from analysis entirely due to e.g. size problems. In these cases, the + * lower or upper bound string tree environment will be returned, depending on the soundness mode of the analysis. + * + * @see [[StructuralAnalysis]], [[DataFlowAnalysis]], [[StringFlowFunctionProperty]], [[StringAnalysisConfig]] + * * @author Maximilian Rüsch */ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAnalysis with StringAnalysisConfig { @@ -61,7 +70,11 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn val state = MethodStringFlowAnalysisState(method, declaredMethods(method), ps(method, TACAI.key)) if (excludedPackages.exists(method.classFile.thisType.packageName.startsWith(_))) { - Result(state.entity, MethodStringFlow.lb) + Result( + state.entity, + if (highSoundness) MethodStringFlow.lb + else MethodStringFlow.ub + ) } else if (state.tacDependee.isRefinable) { InterimResult( state.entity, @@ -82,11 +95,6 @@ class MethodStringFlowAnalysis(override val project: SomeProject) extends FPCFAn } } - /** - * 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(implicit state: MethodStringFlowAnalysisState ): ProperPropertyComputationResult = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisScheduler.scala new file mode 100644 index 0000000000..3cddd596e4 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisScheduler.scala @@ -0,0 +1,58 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package flowanalysis + +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +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.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.MethodStringFlow +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * A shared scheduler trait for analyses that analyse the string flow of given methods. + * + * @see [[MethodStringFlowAnalysis]] + * + * @author Maximilian Rüsch + */ +sealed trait MethodStringFlowAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.ub(MethodStringFlow) + + override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.ub(StringFlowFunctionProperty) + ) + + override final type InitializationData = MethodStringFlowAnalysis + override def init(p: SomeProject, ps: PropertyStore): InitializationData = new MethodStringFlowAnalysis(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 = {} +} + +object LazyMethodStringFlowAnalysis + extends MethodStringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(MethodStringFlow.key, initData.analyze) + initData + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala index abb63c944f..cde53ad181 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/MethodStringFlowAnalysisState.scala @@ -21,9 +21,12 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** - * 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. + * The state of the [[MethodStringFlowAnalysis]] containing data flow analysis relevant information such as flow graphs + * and all string flow dependees for the method under analysis. + * + * @see [[MethodStringFlowAnalysis]] + * + * @author Maximilian Rüsch */ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { @@ -61,7 +64,7 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var else StringFlowFunctionProperty.ub.flow } - private def getWebs: IndexedSeq[PDUWeb] = { + private def webs: IndexedSeq[PDUWeb] = { pcToDependeeMapping.values.flatMap { v => if (v.hasUBP) v.ub.webs else StringFlowFunctionProperty.ub.webs @@ -74,7 +77,6 @@ case class MethodStringFlowAnalysisState(entity: Method, dm: DefinedMethod, var private var _startEnv: StringTreeEnvironment = StringTreeEnvironment(Map.empty) def getStartEnvAndReset(implicit highSoundness: Boolean): StringTreeEnvironment = { if (pcToWebChangeMapping.exists(_._2)) { - val webs = getWebs val indexedWebs = mutable.ArrayBuffer.empty[PDUWeb] val defPCToWebIndex = mutable.Map.empty[Int, Int] webs.foreach { web => diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala index e7b65b7992..27110fd72d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/StructuralAnalysis.scala @@ -21,6 +21,33 @@ import scalax.collection.immutable.Graph import scalax.collection.mutable.{Graph => MutableGraph} /** + * An algorithm that identifies several different types of flow regions in a given flow graph and reduces them to a + * single node iteratively. The algorithm terminates either when a single node is left in the flow graph or such a state + * could not be reached after [[maxIterations]]. + * + * On termination, the [[analyze]] function returns: + *

      + *
    1. + * The reduced flow graph, a single node equal to the root node of the control tree. + *
    2. + *
    3. + * A super flow graph as a combination of the given source flow graph and the control tree. For each + * node contained in the control tree, the super flow graph contains the node itself and edges to its children + * as referenced the control tree. However, its children are still connected with edges as contained in the + * source flow graph. + *
      + * This representation eases traversal for data flow analysis such as by [[DataFlowAnalysis]]. + *
    4. + *
    5. + * The control tree, as a hierarchic representation of the control flow regions identified by the algorithm. + *
    6. + *
    + * + * This algorithm is adapted from Muchnick, S.S. (1997). Advanced Compiler Design and Implementation and optimized for + * performance. + * + * @see [[MethodStringFlowAnalysis]], [[FlowGraphNode]], [[DataFlowAnalysis]] + * * @author Maximilian Rüsch */ object StructuralAnalysis { @@ -31,13 +58,17 @@ object StructuralAnalysis { private type MControlTree = MutableGraph[FlowGraphNode, DiEdge[FlowGraphNode]] private type MSuperFlowGraph = MutableGraph[FlowGraphNode, Edge[FlowGraphNode]] - def analyze(graph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { - val g: MFlowGraph = MutableGraph.from(graph.edges.outerIterable) - val sg: MSuperFlowGraph = MutableGraph.from(graph.edges.outerIterable).asInstanceOf[MSuperFlowGraph] - var curEntry = entry + def analyze(initialGraph: FlowGraph, entry: FlowGraphNode): (FlowGraph, SuperFlowGraph, ControlTree) = { + val flowGraph: MFlowGraph = MutableGraph.from(initialGraph.edges.outerIterable) + val superFlowGraph: MSuperFlowGraph = + MutableGraph.from(initialGraph.edges.outerIterable).asInstanceOf[MSuperFlowGraph] + var currentEntry = entry val controlTree: MControlTree = MutableGraph.empty[FlowGraphNode, DiEdge[FlowGraphNode]] - var (indexedNodes, indexOf, immediateDominators, allDominators) = computeDominators(g, entry) + var (immediateDominators, allDominators) = computeDominators(flowGraph, entry) + /** + * @return True when the given node n strictly dominates the node w. + */ def strictlyDominates(n: FlowGraphNode, w: FlowGraphNode): Boolean = n != w && allDominators(w).contains(n) val knownPartOfNoCycle = mutable.Set.empty[FlowGraphNode] @@ -45,7 +76,9 @@ object StructuralAnalysis { if (knownPartOfNoCycle.contains(n)) { false } else { - val cycleOpt = g.findCycleContaining(g.get(n)) + // IMPROVE if no cycle is found, we can use the visitor of `findCycleContaining` to identify more nodes + // that are known to not be part of a cycle and add them to `knownPartOfNoCycle`. + val cycleOpt = flowGraph.findCycleContaining(flowGraph.get(n)) if (cycleOpt.isDefined) { true } else { @@ -56,7 +89,7 @@ object StructuralAnalysis { } var iterations = 0 - while (g.order > 1 && iterations < maxIterations) { + while (flowGraph.order > 1 && iterations < maxIterations) { // Find post order depth first traversal order for nodes var postCtr = 1 val post = mutable.ListBuffer.empty[FlowGraphNode] @@ -78,10 +111,10 @@ object StructuralAnalysis { knownPartOfNoCycle.subtractAll(subNodes) // Replace edges - val incomingEdges = g.edges.filter { e => + val incomingEdges = flowGraph.edges.filter { e => !subNodes.contains(e.outer.source) && subNodes.contains(e.outer.target) } - val outgoingEdges = g.edges.filter { e => + val outgoingEdges = flowGraph.edges.filter { e => subNodes.contains(e.outer.source) && !subNodes.contains(e.outer.target) } @@ -90,19 +123,16 @@ object StructuralAnalysis { }.concat(outgoingEdges.map { e => OuterEdge[FlowGraphNode, DiEdge[FlowGraphNode]](DiEdge(newRegion, e.outer.target)) }) - g.addAll(newRegionEdges) - g.removeAll(subNodes, Set.empty) + flowGraph.addAll(newRegionEdges) + flowGraph.removeAll(subNodes, Set.empty) - sg.addAll(newRegionEdges) - sg.removeAll { + superFlowGraph.addAll(newRegionEdges) + superFlowGraph.removeAll { incomingEdges.concat(outgoingEdges).map(e => DiEdge(e.outer.source, e.outer.target)) .concat(Seq(DiHyperEdge(OneOrMore(newRegion), OneOrMore.from(subNodes).get))) } // Update dominator data - indexedNodes = indexedNodes.filterNot(subNodes.contains).appended(newRegion) - indexOf = indexedNodes.zipWithIndex.toMap - val commonDominators = subNodes.map(allDominators).reduce(_.intersect(_)) allDominators.subtractAll(subNodes).update(newRegion, commonDominators) allDominators = allDominators.map(kv => @@ -122,24 +152,31 @@ object StructuralAnalysis { controlTree.addAll(subNodes.map(node => OuterEdge[FlowGraphNode, DiEdge[FlowGraphNode]](DiEdge(newRegion, node)) )) - if (subNodes.contains(curEntry)) { - curEntry = newRegion + if (subNodes.contains(currentEntry)) { + currentEntry = newRegion } } - val ordering = g.NodeOrdering((in1, in2) => in1.compare(in2)) - g.innerNodeDownUpTraverser(g.get(curEntry), Parameters(DepthFirst), ordering = ordering).foreach { + // Determine post-order depth-first traversal in the given graph + val ordering = flowGraph.NodeOrdering((in1, in2) => in1.compare(in2)) + flowGraph.innerNodeDownUpTraverser( + flowGraph.get(currentEntry), + Parameters(DepthFirst), + ordering = ordering + ).foreach { case (down, in) if !down => post.append(in.outer) case _ => } - while (g.order > 1 && postCtr < post.size) { + while (flowGraph.order > 1 && postCtr < post.size) { var n = post(postCtr) val gPostMap = - post.reverse.zipWithIndex.map(ni => (g.get(ni._1).asInstanceOf[MFlowGraph#NodeT], ni._2)).toMap + post.reverse.zipWithIndex.map(ni => + (flowGraph.get(ni._1).asInstanceOf[MFlowGraph#NodeT], ni._2) + ).toMap val (newStartingNode, acyclicRegionOpt) = - locateAcyclicRegion[FlowGraphNode, MFlowGraph](g, gPostMap, allDominators)(n) + locateAcyclicRegion[FlowGraphNode, MFlowGraph](flowGraph, gPostMap, allDominators)(n) n = newStartingNode if (acyclicRegionOpt.isDefined) { val (arType, nodes, entry) = acyclicRegionOpt.get @@ -147,16 +184,16 @@ object StructuralAnalysis { } else if (inCycle(n)) { var reachUnder = Set(n) for { - m <- g.nodes.outerIterator + m <- flowGraph.nodes.outerIterator if m != n innerM = controlTree.find(m) if innerM.isEmpty || !innerM.get.hasPredecessors - if StructuralAnalysis.pathBack[FlowGraphNode, MFlowGraph](g, strictlyDominates)(m, n) + if StructuralAnalysis.pathBack[FlowGraphNode, MFlowGraph](flowGraph, strictlyDominates)(m, n) } { reachUnder = reachUnder.incl(m) } - val cyclicRegionOpt = locateCyclicRegion[FlowGraphNode, MFlowGraph](g, n, reachUnder) + val cyclicRegionOpt = locateCyclicRegion[FlowGraphNode, MFlowGraph](flowGraph, n, reachUnder) if (cyclicRegionOpt.isDefined) { val (crType, nodes, entry) = cyclicRegionOpt.get replace(nodes, entry, crType) @@ -176,16 +213,23 @@ object StructuralAnalysis { } ( - Graph.from(g.edges.outerIterable), - Graph.from(sg.edges.outerIterable), + Graph.from(flowGraph.edges.outerIterable), + Graph.from(superFlowGraph.edges.outerIterable), Graph.from(controlTree.edges.outerIterable) ) } + /** + * Computes the immediate and global dominators for each of the nodes in the given graph. + * + * @param graph The graph to compute dominators for. + * @param entry The entry node to the graph under analysis. + * @return (A map for immediate dominators by graph node, A map for all dominators of a graph node by graph node) + */ private def computeDominators[A, G <: MutableGraph[A, DiEdge[A]]]( graph: G, entry: A - ): (IndexedSeq[A], Map[A, Int], mutable.Map[A, A], mutable.Map[A, Seq[A]]) = { + ): (mutable.Map[A, A], mutable.Map[A, Seq[A]]) = { val indexedNodes = graph.nodes.toIndexedSeq val indexOf = indexedNodes.zipWithIndex.toMap val domTree = DominatorTree( @@ -215,9 +259,20 @@ object StructuralAnalysis { } val allDominators = immediateDominators.map(kv => (kv._1, getAllDominators(kv._2))) - (outerIndexedNodes, indexOf.map(kv => (kv._1.outer, kv._2)), immediateDominators, allDominators) + (immediateDominators, allDominators) } + /** + * Determines if a path exists from the given node m to some other node k that does not contain the node n AND an + * edge k -> n exists that is a back edge in the given graph. The latter is realized by using predecessors to find + * eligible edges and using a strict domination predicate to determine a back edge from it. + * + * @param graph The graph under analysis. + * @param strictlyDominates Predicate if a given first node strictly dominates the given second nodes in the given graph. + * @param m The node that forms the starting node of the path. + * @param n The node that forms the ending node of the path. + * @return True if there is path back from m to n over some intermediate node k where m -> k does not contain n. + */ private def pathBack[A, G <: MutableGraph[A, DiEdge[A]]](graph: G, strictlyDominates: (A, A) => Boolean)( m: A, n: A @@ -233,6 +288,19 @@ object StructuralAnalysis { } } + /** + * Identifies a candidate acyclic region if one can be found starting the identification at the given start node. + * If no region can be found, a new starting node is returned that can be used to resume searching for other region + * types. If a region can be found, all information needed to replace the contained nodes with a new region node in + * the graph is returned. + * + * @param graph The graph under analysis. + * @param postOrderTraversal A post order DFS traversal of the given graph as a mapping from node to its DF position. + * @param allDominators A dominator index as produced by [[computeDominators]]. + * @param startingNode The node to start locating the acyclic region at. + * @return The new starting node of a candidate region as well as an option of: 1. The type of the found acyclic + * region, 2. a set containing all region nodes and 3. the entry node to the region. + */ private def locateAcyclicRegion[A <: FlowGraphNode, G <: MutableGraph[A, DiEdge[A]]]( graph: G, postOrderTraversal: Map[G#NodeT, Int], @@ -276,7 +344,7 @@ object StructuralAnalysis { val dominatedNodes = allDominators.filter(_._2.contains(n.outer)).map(kv => graph.get(kv._1)).toSet ++ Set(n) if (dominatedNodes.size == 1 || !isAcyclic(dominatedNodes) || - // Check if no dominated node is reached from an non-dominated node + // Check if no dominated node is reached from a non-dominated node !dominatedNodes.excl(n).forall(_.diPredecessors.subsetOf(dominatedNodes)) || // Check if all dominated nodes agree on a single successor outside the set (if it exists) dominatedNodes.flatMap(node => node.diSuccessors.diff(dominatedNodes)).size > 1 @@ -292,7 +360,7 @@ object StructuralAnalysis { val newDirectSuccessors = n.diSuccessors val rType = if (nSet.size > 1) { - // Condition is added to ensure chosen bb does not contain any self loops or other cyclic stuff + // Condition is added to ensure chosen bb does not contain any self loops or other cyclic regions // IMPROVE weaken to allow back edges from the "last" nSet member to the first to enable reductions to self loops if (isAcyclic(nSet)) Some(Block) @@ -333,9 +401,22 @@ object StructuralAnalysis { None } - (n.outer, rType.map((_, nSet.map(_.outer), entry))) + (n.outer, rType.map((_, nSet.map(_.outer), entry.outer))) } + /** + * Identifies a candidate cyclic region if one can be found starting the identification at the given start node. + * + * @param graph The graph under analysis. + * @param startingNode The node to start identifying cyclic regions at. + * @param reachUnder A set of all nodes that can be reached starting from the given starting node that have a path + * back to the given start node. + * @return An option of: 1. The cyclic region type, 2. the nodes contained in the region and 3. the entry node of + * the region. + * + * @note This implementation does not yet support improper regions and their reduction and will throw upon their + * detection. + */ private def locateCyclicRegion[A, G <: MutableGraph[A, DiEdge[A]]]( graph: G, startingNode: A, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala index 49135cc4b8..9208912ace 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/flowanalysis/package.scala @@ -34,13 +34,26 @@ package object flowanalysis { object FlowGraph extends TypedGraphFactory[FlowGraphNode, DiEdge[FlowGraphNode]] { - def entry: FlowGraphNode = GlobalEntry + /** + * The entry point of all flow graphs. + * + * @see [[GlobalEntry]] + */ + val entry: FlowGraphNode = GlobalEntry private def mapInstrIndexToPC[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]])(index: Int): Int = { if (index >= 0) cfg.code.instructions(index).pc else index } + /** + * Converts a given CFG to a flow graph with additional global entry and exit nodes. + * + * @see [[FlowGraphNode]] + * + * @param cfg The CFG to convert to a flow graph. + * @return The flow graph obtained from the CFG. + */ def apply[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): FlowGraph = { val toPC = mapInstrIndexToPC(cfg) _ @@ -90,6 +103,10 @@ package object flowanalysis { private[this] def entryFromCFG[V <: Var[V]](cfg: CFG[Stmt[V], TACStmts[V]]): Statement = Statement(mapInstrIndexToPC(cfg)(cfg.startBlock.nodeId)) + /** + * @param graph The graph consisting of flow graph nodes to convert to DOT format. + * @return A DOT string of the graph. + */ def toDot[N <: FlowGraphNode, E <: Edge[N]](graph: Graph[N, E]): String = { val root = DotRootGraph( directed = true, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala index 80b70954d2..e361d14ebd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationHandler.scala @@ -17,10 +17,11 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * Processes expressions that are relevant in order to determine which value(s) the string value at a given def site - * might have. + * might have. Produces string flow functions that transform a given string state during data flow analysis. * - * [[InterpretationHandler]]s of any level may use [[StringInterpreter]]s from their level or any level below. - * [[StringInterpreter]]s defined in the [[interpretation]] package may be used by any level. + * @note [[InterpretationHandler]]s of any level may use [[StringInterpreter]]s from their level or any level below. + * + * @see [[StringFlowFunctionProperty]], [[org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis]] * * @author Maximilian Rüsch */ @@ -71,9 +72,7 @@ abstract class InterpretationHandler extends FPCFAnalysis with StringAnalysisCon processStatement(state)(state.tac.stmts(defSiteOpt.get)) } - protected def processStatement( - implicit state: InterpretationState - ): PartialFunction[Stmt[V], ProperPropertyComputationResult] + protected def processStatement(implicit state: InterpretationState): Stmt[V] => ProperPropertyComputationResult } object InterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala index b2fdfd4662..85f41cdfd7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/InterpretationState.scala @@ -11,6 +11,16 @@ import org.opalj.br.Method import org.opalj.fpcf.EOptionP import org.opalj.tac.fpcf.properties.TACAI +/** + * The state for the FPCF analysis responsible for interpreting the statement at the given PC of the given method and + * obtaining its string flow information. + * + * @see [[InterpretationHandler]], [[StringInterpreter]] + * + * @param pc The PC of the statement under analysis. + * @param dm The method of the statement under analysis. + * @param tacDependee The initial TACAI dependee of the method under analysis. + */ case class InterpretationState(pc: Int, dm: DefinedMethod, var tacDependee: EOptionP[Method, TACAI]) { def tac: TAC = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/StringFlowAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/StringFlowAnalysisScheduler.scala new file mode 100644 index 0000000000..5bd4f4c63d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/interpretation/StringFlowAnalysisScheduler.scala @@ -0,0 +1,54 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package string +package interpretation + +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +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.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty + +/** + * A general scheduler trait for the different string flow analysis levels. + * + * @see [[InterpretationHandler]] + * + * @author Maximilian Rüsch + */ +sealed trait StringFlowAnalysisScheduler extends FPCFAnalysisScheduler { + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringFlowFunctionProperty) + + override def uses: Set[PropertyBounds] = PropertyBounds.ubs(TACAI) + + override final type InitializationData = InterpretationHandler + + 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 = {} +} + +trait LazyStringFlowAnalysis + extends StringFlowAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register(p: SomeProject, ps: PropertyStore, initData: InitializationData): FPCFAnalysis = { + ps.registerLazyPropertyComputation(StringFlowFunctionProperty.key, initData.analyze) + + initData + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala index e60951dece..bf3d75c2d7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/L0StringAnalysis.scala @@ -9,9 +9,12 @@ package l0 import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.flowanalysis.LazyMethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.LazyStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0InterpretationHandler /** + * @see [[L0InterpretationHandler]] * @author Maximilian Rüsch */ object LazyL0StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala index b06fd8210c..75f0b279e9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/BinaryExprInterpreter.scala @@ -17,6 +17,10 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** + * Interprets the given assignment statement containing a [[BinaryExpr]] by determining its return type and using the + * appropriate dynamic [[StringTreeNode]] in high soundness mode. In low soundness mode, no string value can be + * determined. + * * @author Maximilian Rüsch */ case class BinaryExprInterpreter()( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala index 0ebf8bd903..b4cf9c74e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/L0InterpretationHandler.scala @@ -16,13 +16,16 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc * + * Interprets statements on a very basic level by only interpreting either constant or binary expressions and their + * resulting assignments. + * * @author Maximilian Rüsch */ class L0InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { override protected def processStatement(implicit state: InterpretationState - ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + ): Stmt[V] => ProperPropertyComputationResult = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala index 03d708fe53..84c888e956 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l0/interpretation/SimpleValueConstExprInterpreter.scala @@ -13,6 +13,11 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** + * Interprets the given assignment statement containing a [[SimpleValueConst]] expression by determining the possible + * constant values from the given expression. The result is converted to a [[StringTreeConst]] and applied to the + * assignment target variable in the string flow function. If no applicable const is found, ID is returned for all + * variables. + * * @author Maximilian Rüsch */ object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter { @@ -22,8 +27,6 @@ object SimpleValueConstExprInterpreter extends AssignmentBasedStringInterpreter override def interpretExpr(target: PV, expr: E)(implicit state: InterpretationState ): ProperPropertyComputationResult = { - // IMPROVE the constants generated here could also be interpreted outside string flow interpretation, reducing - // the overhead needed to interpret a simple string const for e.g. a call parameter. computeFinalResult(expr match { case ic: IntConst => StringFlowFunctionProperty.constForVariableAt(state.pc, target, StringTreeConst(ic.value.toString)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala index 0216e74060..14b045d2d4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/L1StringAnalysis.scala @@ -9,9 +9,12 @@ package l1 import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.flowanalysis.LazyMethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.LazyStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHandler /** + * @see [[L1InterpretationHandler]] * @author Maximilian Rüsch */ object LazyL1StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala index 72b9d9325c..daa2ffe0ad 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1FunctionCallInterpreter.scala @@ -26,6 +26,9 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** + * Base trait for all function call interpreters on L1. Provides support for multiple possible called methods as well as + * adding called methods and return dependees at runtime. + * * @author Maximilian Rüsch */ trait L1FunctionCallInterpreter @@ -120,14 +123,17 @@ trait L1FunctionCallInterpreter StringTreeOr { callState.calleeMethods.map { m => if (callState.hasUnresolvableReturnValue(m)) { + // We know we cannot resolve a definitive return value for this function failureTree } else if (callState.returnDependees.contains(m)) { + // We have some return dependees and can thus join their state StringTreeOr(callState.returnDependees(m).map { rd => if (rd.hasUBP) { rd.ub.tree.replaceParameters(parameters.map { kv => (kv._1, env(pc, kv._2)) }) } else StringTreeNode.ub }) } else { + // Empty join -> Upper bound StringTreeNode.ub } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala index 9c08de5d7d..27afbac4b3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1InterpretationHandler.scala @@ -18,13 +18,16 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc * + * Interprets statements similar to the [[org.opalj.tac.fpcf.analyses.string.l0.interpretation.L0InterpretationHandler]] + * but handles all sorts of function calls on top. + * * @author Maximilian Rüsch */ class L1InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { override protected def processStatement(implicit state: InterpretationState - ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + ): Stmt[V] => ProperPropertyComputationResult = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) case stmt @ Assignment(_, _, expr: BinaryExpr[V]) => BinaryExprInterpreter().interpretExpr(stmt, expr) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala index 50f8bcb271..5a908ebabc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1NonVirtualMethodCallInterpreter.scala @@ -15,6 +15,9 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** + * Processes [[NonVirtualMethodCall]]s without a call graph. Currently, only calls to `` of strings, string + * buffers and string builders are interpreted. For other calls, ID is returned. + * * @author Maximilian Rüsch */ case class L1NonVirtualMethodCallInterpreter()( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 4ae2dcf1dc..34398108b8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -18,6 +18,12 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunction import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment /** + * Interprets some specific static calls in the context of their method as well as arbitrary static calls without a call + * graph. + * + * @see [[L1ArbitraryStaticFunctionCallInterpreter]], [[L1StringValueOfFunctionCallInterpreter]], + * [[L1SystemPropertiesInterpreter]] + * * @author Maximilian Rüsch */ case class L1StaticFunctionCallInterpreter()( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala index 9e83d66001..3f6f10c653 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1SystemPropertiesInterpreter.scala @@ -21,6 +21,11 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty +/** + * Processes assignments of system property references to variables by using the [[SystemProperties]] FPCF property. + * + * @author Maximilian Rüsch + */ private[string] trait L1SystemPropertiesInterpreter extends StringInterpreter { implicit val ps: PropertyStore diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala index e8dc7a237b..0028340b9e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualFunctionCallInterpreter.scala @@ -27,7 +27,11 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue /** - * Responsible for processing [[VirtualFunctionCall]]s without a call graph. + * Processes [[VirtualFunctionCall]]s without a call graph. Some string operations such as `append`, `toString` or + * `substring` are either fully interpreted or approximated. + * + * @note Due to a missing call graph, arbitrary (i.e. not otherwise interpreted) virtual function calls will not be + * interpreted. * * @author Maximilian Rüsch */ @@ -110,6 +114,8 @@ private[string] trait L1ArbitraryVirtualFunctionCallInterpreter extends Assignme /** * Interprets calls to [[StringBuilder#append]] or [[StringBuffer#append]]. + * + * @author Maximilian Rüsch */ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringInterpreter { @@ -157,6 +163,8 @@ private[string] trait L1AppendCallInterpreter extends AssignmentLikeBasedStringI /** * Interprets calls to [[String#substring]]. + * + * @author Maximilian Rüsch */ private[string] trait L1SubstringCallInterpreter extends AssignmentLikeBasedStringInterpreter { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala index 9b4339fa44..cbcc7aa6a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1VirtualMethodCallInterpreter.scala @@ -16,6 +16,9 @@ import org.opalj.tac.fpcf.properties.string.StringTreeEnvironment import org.opalj.value.TheIntegerValue /** + * Processes [[VirtualMethodCall]]s without a call graph. Currently, only calls to `setLength` of string buffers and + * string builders are interpreted. For other calls, ID is returned. + * * @author Maximilian Rüsch */ case class L1VirtualMethodCallInterpreter()( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala index d1f7e89d07..ffc1512344 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/L2StringAnalysis.scala @@ -14,9 +14,12 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.flowanalysis.LazyMethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.LazyStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l2.interpretation.L2InterpretationHandler /** + * @see [[L2InterpretationHandler]] * @author Maximilian Rüsch */ object LazyL2StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala index 10400cf63d..3b6f877d46 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l2/interpretation/L2InterpretationHandler.scala @@ -25,6 +25,11 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc * + * Interprets statements similar to [[org.opalj.tac.fpcf.analyses.string.l1.interpretation.L1InterpretationHandler]] but + * handles virtual function calls using the call graph. + * + * @note This level can be expanded to handle all function calls via the call graph, not just virtual ones. + * * @author Maximilian Rüsch */ class L2InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { @@ -33,7 +38,7 @@ class L2InterpretationHandler(implicit override val project: SomeProject) extend override protected def processStatement(implicit state: InterpretationState - ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + ): Stmt[V] => ProperPropertyComputationResult = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala index f22394874f..9d524357d6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/L3StringAnalysis.scala @@ -14,9 +14,12 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.string.flowanalysis.LazyMethodStringFlowAnalysis +import org.opalj.tac.fpcf.analyses.string.interpretation.LazyStringFlowAnalysis import org.opalj.tac.fpcf.analyses.string.l3.interpretation.L3InterpretationHandler /** + * @see [[L3InterpretationHandler]] * @author Maximilian Rüsch */ object LazyL3StringAnalysis { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala index 034cea4504..fe89cee64c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3FieldReadInterpreter.scala @@ -33,8 +33,8 @@ import org.opalj.tac.fpcf.analyses.string.interpretation.InterpretationState import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** - * Responsible for processing direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields - * via the [[FieldWriteAccessInformation]]. + * Interprets direct reads to fields (see [[FieldRead]]) by analyzing the write accesses to these fields via the + * [[FieldWriteAccessInformation]] and the possible string values passed to these write accesses. * * @author Maximilian Rüsch */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala index fecaa358d7..c426cb393f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l3/interpretation/L3InterpretationHandler.scala @@ -28,6 +28,9 @@ import org.opalj.tac.fpcf.properties.string.StringFlowFunctionProperty /** * @inheritdoc * + * Interprets statements similar to [[org.opalj.tac.fpcf.analyses.string.l2.interpretation.L2InterpretationHandler]] but + * handles field read accesses as well. + * * @author Maximilian Rüsch */ class L3InterpretationHandler(implicit override val project: SomeProject) extends InterpretationHandler { @@ -37,7 +40,7 @@ class L3InterpretationHandler(implicit override val project: SomeProject) extend override protected def processStatement(implicit state: InterpretationState - ): PartialFunction[Stmt[V], ProperPropertyComputationResult] = { + ): Stmt[V] => ProperPropertyComputationResult = { case stmt @ Assignment(_, _, expr: SimpleValueConst) => SimpleValueConstExprInterpreter.interpretExpr(stmt, expr) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index 91112f8642..e51eced3ba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -35,9 +35,9 @@ import org.opalj.tac.fpcf.properties.TACAI * If the variable represents any other value, no string value can be derived and the analysis returns either the upper * or lower bound depending on the soundness mode. * - * @author Maximilian Rüsch - * * @see [[StringAnalysis]] + * + * @author Maximilian Rüsch */ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnalysis with StringAnalysisConfig { @@ -105,9 +105,9 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly } /** - * @author Maximilian Rüsch - * * @see [[TrivialStringAnalysis]] + * + * @author Maximilian Rüsch */ object LazyTrivialStringAnalysis extends BasicFPCFLazyAnalysisScheduler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala index 95b83e0947..f8c6eee582 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesAnalysis.scala @@ -35,20 +35,24 @@ import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.properties.TACAI /** + * An FPCF analysis that analyses reachable methods for calls that modify [[java.util.Properties]], including the + * system properties given on [[System.setProperty]]. + * + * @see [[SystemProperties]] + * * @author Maximilian Rüsch */ class SystemPropertiesAnalysis private[analyses] ( final val project: SomeProject ) extends ReachableMethodAnalysis { - type State = SystemPropertiesState[ContextType] - private type Values = Set[StringTreeNode] + private type State = SystemPropertiesState[ContextType] def processMethod(callContext: ContextType, tacaiEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { // IMPROVE add initialization framework similar to the EntryPointFinder framework - implicit val state: State = new SystemPropertiesState(callContext, tacaiEP, Map.empty) + implicit val state: State = new SystemPropertiesState(callContext, tacaiEP) - var values: Values = Set.empty + var values: Set[StringTreeNode] = Set.empty for (stmt <- state.tac.stmts) stmt match { case VirtualFunctionCallStatement(call) if (call.name == "setProperty" || call.name == "put") && @@ -124,6 +128,11 @@ class SystemPropertiesAnalysis private[analyses] ( } } +/** + * A scheduler for the reachable method [[SystemPropertiesAnalysis]] that is triggered on the [[Callers]] property. + * + * @author Maximilian Rüsch + */ object TriggeredSystemPropertiesAnalysisScheduler extends BasicFPCFTriggeredAnalysisScheduler { override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeIteratorKey) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala index b442ff16cc..763b77b421 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/systemproperties/SystemPropertiesState.scala @@ -14,28 +14,22 @@ import org.opalj.tac.fpcf.analyses.cg.BaseAnalysisState import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.properties.TACAI +/** + * @see [[SystemPropertiesAnalysis]] + * @author Maximilian Rüsch + */ final class SystemPropertiesState[ContextType <: Context]( - override val callContext: ContextType, - override protected[this] var _tacDependee: EOptionP[Method, TACAI], - private[this] var _stringConstancyDependees: Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] + override val callContext: ContextType, + override protected[this] var _tacDependee: EOptionP[Method, TACAI] ) extends BaseAnalysisState with TACAIBasedAnalysisState[ContextType] { - ///////////////////////////////////////////// - // // - // strings // - // // - ///////////////////////////////////////////// + private[this] var _stringConstancyDependees: Map[VariableContext, EOptionP[VariableContext, StringConstancyProperty]] = + Map.empty def updateStringDependee(dependee: EOptionP[VariableContext, StringConstancyProperty]): Unit = { _stringConstancyDependees = _stringConstancyDependees.updated(dependee.e, dependee) } - ///////////////////////////////////////////// - // // - // general dependency management // - // // - ///////////////////////////////////////////// - override def hasOpenDependencies: Boolean = { super.hasOpenDependencies || _stringConstancyDependees.valuesIterator.exists(_.isRefinable) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala index 330fd582e9..6dd88960a6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/MethodStringFlow.scala @@ -11,6 +11,11 @@ import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation /** + * An FPCF property that captures the string flow results of an entire method based on the final [[StringTreeEnvironment]] + * after executing the [[org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis]]. + * + * @see [[StringTreeEnvironment]] + * * @author Maximilian Rüsch */ sealed trait MethodStringFlowPropertyMetaInformation extends PropertyMetaInformation { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala index 753efc62d2..dada85d814 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringFlowFunction.scala @@ -11,6 +11,10 @@ import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation /** + * An FPCF property representing a string flow function at a fixed [[org.opalj.tac.fpcf.analyses.string.MethodPC]] to be + * used during [[org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis]]. Can be produced by e.g. + * [[org.opalj.tac.fpcf.analyses.string.StringInterpreter]]s. + * * @author Maximilian Rüsch */ sealed trait StringFlowFunctionPropertyMetaInformation extends PropertyMetaInformation { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala index 8ce92422eb..423e57a453 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/string/StringTreeEnvironment.scala @@ -11,6 +11,9 @@ import org.opalj.br.fpcf.properties.string.SetBasedStringTreeOr import org.opalj.br.fpcf.properties.string.StringTreeNode /** + * A mapping from [[PDUWeb]] to [[StringTreeNode]], used to identify the state of string variables during a given fixed + * point of the [[org.opalj.tac.fpcf.analyses.string.flowanalysis.DataFlowAnalysis]]. + * * @author Maximilian Rüsch */ case class StringTreeEnvironment private (