"findMethods" doesn't return expected results - call-graph

I'm trying to implement an analysis (extends DefaultOneStepAnalysis) to construct call graph in CHA algorithms. There are three parts of my code:
1) method "doAnalyze" to return the "BasicReport"
2) method "analyze" to find call edges for each method in the given project
3) class "AnalysisContext" to store the context and methods using in the analysis.
In 3), I use method "callBySignature" to find out cbsMethods of a method same as in "CHACallGraphExtractor" but it doesn't return expected result.
While I use the original OPAL's way to get cbsMethods in Extractor, the result is a set of methods.
Could you please help me to confirm where the problem is and how to solve it?
Thank you very much.
Regards,
Jiang
----Main Part of my code-------------------------------------------------
object CHACGAnalysis extends DefaultOneStepAnalysis {
... ...
override def doAnalyze(
project: Project[URL],
parameters: Seq[String] = List.empty,
isInterrupted: () ⇒ Boolean
): BasicReport = {
... ...
for {
classFile <- project.allProjectClassFiles
method <- classFile.methods
} {
analyze(project, methodToCellCompleter, classFile, method))
}
... ...
}
def analyze(
project: Project[URL],
methodToCellCompleter: Map[(String,Method), CellCompleter[K, Set[Method]]],
classFile: ClassFile,
method: Method
): Unit = {
… …
val context = new AnalysisContext(project, classFile, method)
method.body.get.foreach((pc, instruction) ⇒
instruction.opcode match {
... ...
case INVOKEINTERFACE.opcode ⇒
val INVOKEINTERFACE(declaringClass, name, descriptor) = instruction
context.addCallEdge_VirtualCall(pc, declaringClass, name, descriptor, true,cell1)
... ...
}
… …
}
protected[this] class AnalysisContext(
val project: SomeProject,
val classFile: ClassFile,
val method: Method
) {
val classHierarchy = project.classHierarchy
val cbsIndex = project.get(CallBySignatureResolutionKey)
val statistics = project.get(IntStatisticsKey)
val instantiableClasses = project.get(InstantiableClassesKey)
val cache = new CallGraphCache[MethodSignature, scala.collection.Set[Method]](project)
private[AnalysisContext] def callBySignature(
declaringClassType: ObjectType,
name: String,
descriptor: MethodDescriptor
): Set[Method] = {
val cbsMethods = cbsIndex.findMethods(
name,
descriptor,
declaringClassType
)
cbsMethods
}
def addCallEdge_VirtualCall(
pc: PC,
declaringClassType: ObjectType,
name: String,
descriptor: MethodDescriptor,
isInterfaceInvocation: Boolean = false,
cell1: CellCompleter[K, Set[Method]]
): Unit = {
val cbsCalls =
if (isInterfaceInvocation) {
callBySignature(declaringClassType, name, descriptor)
}
else
Set.empty[Method]
… …
}
… …
}

Finally, I have found the problem is due to "AnalysisMode"
After I resetting AnalysisMode into "CPA“ the question is solved.
I think I should always keep in mind what "AnalysisMode" should be used before I design the algorithm.
Thank you for your concern
Jiang

Related

Kotlin: How to define a variable whose type depends on the input?

I have a function in Kotlin which takes a particular string as input. Depending on the input, I want to create a variable of a specific type and do some computations on it.
For example,
fun compute(input: String): Any{
if(input=="2d"){
var point: Point2D;// Points2D - x: int, y: int
//initilize and do some computations
return point.findDistanceFromOrigin()
}else if(input=="2d-1"){
var point: Point2DWithP1AsOrigin;// Point2DWithP1AsOrigin - x: int, y: int
//initilize and do some computations
return point.findDistanceFromOrigin()
}else if(input=="2d-2"){
var point: Point2DWithP2AsOrigin;
//initilize and do some computations
return point.findDistanceFromOrigin()
}
.
.
.
}
You can see in the above example, I want to initilize the type of point depending on the input and do computation and return.
All the if-else conditions have the same code except for the definition of the variable. How can I put all this in a single block with something like this:
var point: if(input=="2d) Point2D::class else if(input=="2d-1") Point2DWithP1AsOrigin::class.....
How can I do that?
You could do something like this
fun compute(input: String): Any{
val point: MyPoint = when(input) {
"2d" -> Point2D()
"2d-1" -> Point2DWithP1AsOrigin()
"2d-2" -> Point2DWithP2AsOrigin()
else -> Point2D() //fallback is necessary
}
//initilize and do some computations
return point.findDistanceFromOrigin()
}
But then it's essential that all those classes share the same interface. Because they need to have the same methods in order to do the same operations on them.
For example like this:
class Point2D : MyPoint {
override fun findDistanceFromOrigin() = 5
}
class Point2DWithP1AsOrigin : MyPoint{
override fun findDistanceFromOrigin() = 6
}
class Point2DWithP2AsOrigin : MyPoint{
override fun findDistanceFromOrigin() = 7
}
interface MyPoint {
fun findDistanceFromOrigin() : Int
}
You can store constructor references and then invoke required one
fun main() {
val constructors = mapOf(
"2d" to ::Point2D,
"2d-1" to ::Point2DWithP1AsOrigin,
"2d-2" to ::Point2DWithP2AsOrigin,
)
val type = "2d-2"
val constructor = constructors[type] ?: throw IllegalArgumentException("$type not supported")
val point = constructor()
println(point::class)
}
Output
class Point2DWithP2AsOrigin

Creating an object builder with error handling using Arrow - Pattern match multiple Eithers

I have class A:
class A (private var z: String, private var y: String, private var x: Int)
I want to create a failsafe builder for it. The builder should return Either the list of Exceptions (e.g. when values are missing) or the created values. What is the recommended way to create something like this? Or is there a conceptually better approach?
My own approach to it:
sealed class ABuilderException {
object MissingXValue : ABuilderException()
object MissingYValue : ABuilderException()
object MissingZValue : ABuilderException()
}
import arrow.core.Either
import arrow.core.Option
import arrow.core.none
import arrow.core.some
class ABuilder {
private var x : Option<Int> = none()
private var y : Option<String> = none()
private var z : Option<String> = none()
fun withX(x : Int) : ABuilder {
this.x = x.some();
return this;
}
fun withY(y : String) : ABuilder {
this.y = y.some();
return this;
}
fun withZ(z : String) : ABuilder {
this.z = z.some();
return this;
}
fun build() : Either<A, List<ABuilderException>> {
var xEither = x.toEither { ABuilderException.MissingXValue }
var yEither = y.toEither { ABuilderException.MissingYValue }
var zEither = z.toEither { ABuilderException.MissingZValue }
// If all values are not an exception, create A
// otherwise: Return the list of exceptions
}
}
How could I best complete the build code?
I favor a solution that avoids deep nesting (e.g. orElse or similar methods) and avoids repeating values (e.g. by recreating Tuples), because this may lead to typos and makes it harder to add/remove properties later.
First you need to change the signature of build to:
fun build() : Either<List<ABuilderException>, A>
The reason for doing that is because Either is right biased - functions like map, flatMap etc operate on the Right value and are no-op in case the value is Left.
For combining Either values you can use zip:
val e1 = 2.right()
val e2 = 3.right()
// By default it gives you a `Pair` of the two
val c1 = e1.zip(e2) // Either.Right((2, 3))
// Or you can pass a custom combine function
val c2 = e1.zip(e2) { two, three -> two + three } // Either.Right(5)
However there is an issue here, in case of an error (one of them is Left) it will fail fast and give you only the first one.
To accumulate the errors we can use Validated:
val x = none<Int>()
val y = none<String>()
val z = none<String>()
// Validated<String, Int>
val xa = Validated.fromOption(x) { "X is missing" }
// Validated<String, String>
val ya = Validated.fromOption(y) { "Y is missing" }
// Validated<String, String>
val za = Validated.fromOption(z) { "Z is missing" }
xa.toValidatedNel().zip(
ya.toValidatedNel(),
za.toValidatedNel()
) { x, y, z -> TODO() }
Validated, like Either has a zip function for combining values. The difference is that Validated will accumulate the errors. In the lambda you have access to the valid values (Int, String, String) and you can create your valid object.
toValidatedNel() here converts from Validated<String, String> to Validated<Nel<String>, String> where Nel is a list that can NOT be empty. Accumulating errors as a List is common so it's built in.
For more you can check the Error Handling tutorial in the docs.

How do I bind a custom property to a textfield bidirectionally?

I have a complex object that I want to display in a textfield. This is working fine with a stringBinding. But I don't know how to make it two-way so that the textfield is editable.
package com.example.demo.view
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
class MainView : View("Hello TornadoFX") {
val complexThing: Int = 1
val complexProperty = SimpleObjectProperty<Int>(complexThing)
val complexString = complexProperty.stringBinding { complexProperty.toString() }
val plainString = "asdf"
val plainProperty = SimpleStringProperty(plainString)
override val root = vbox {
textfield(complexString)
label(plainProperty)
textfield(plainProperty)
}
}
When I run this, the plainString is editable and I see the label change because the edits are going back into the property.
How can I write a custom handler or what class do I need to use to make the stringBinding be read and write? I looked through a lot of the Property and binding documentation but did not see anything obvious.
Ta-Da
class Point(val x: Int, val y: Int) //You can put properties in constructor
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
if(string.isNullOrBlank()) return null //Empty strings aren't valid
val xy = string.split(",", limit = 2) //Only using 2 coordinate values so max is 2
if(xy.size < 2) return null //Min values is also 2
val x = xy[0].trim().toIntOrNull() //Trim white space, try to convert
val y = xy[1].trim().toIntOrNull()
return if(x == null || y == null) null //If either conversion fails, count as invalid
else Point(x, y)
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6) //Probably doesn't need to be its own member
val pointProperty = SimpleObjectProperty<Point>(point)
val pc = PointConverter()
override val root = vbox {
label(pointProperty, converter = pc) //Avoid extra properties, put converter in construction
textfield(pointProperty, pc)
}
}
I made edits to your converter to "account" for invalid input by just returning null. This is just a simple band-aid solution that doesn't enforce correct input, but it does refuse to put bad values in your property.
This can probably be done more cleanly. I bet there is a way around the extra property. The example is fragile because it doesn't do input checking in the interest of keeping it simple. But it works to demonstrate the solution:
class Point(x: Int, y: Int) {
val x: Int = x
val y: Int = y
}
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
val xy = string?.split(",")
return Point(xy[0].toInt(), xy[1].toInt())
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6)
val pointProperty = SimpleObjectProperty<Point>(point)
val pointDisplayProperty = SimpleStringProperty()
val pointStringProperty = SimpleStringProperty()
val pc = PointConverter()
init {
pointDisplayProperty.set(pc.toString(pointProperty.value))
pointStringProperty.set(pc.toString(pointProperty.value))
pointStringProperty.addListener { observable, oldValue, newValue ->
pointProperty.set(pc.fromString(newValue))
pointDisplayProperty.set(pc.toString(pointProperty.value))
}
}
override val root = vbox {
label(pointDisplayProperty)
textfield(pointStringProperty)
}
}

Kotlin: how to pass an object function as parameter to another?

I am trying to learn functional Kotlin and have written this test code:
import java.util.*
data class BorrowerX(val name: String, val maxBooks: Int) {
companion object {
fun getName(br: BorrowerX): String = br.name
fun findBorrowerX(n: String, brs: ArrayList<BorrowerX>): BorrowerX? {
val coll: List<BorrowerX> = brs.filter { BorrowerX.getName(it) == n }
if (coll.isEmpty()) {
return null
} else return coll.first()
}
fun findBorrowerX2(n: String, brs: ArrayList<BorrowerX>, f: (BorrowerX) -> String): BorrowerX? {
val coll: List<BorrowerX> = brs.filter { f(it) == n }
if (coll.isEmpty()) {
return null
} else return coll.first()
}
}
}
In the REPL I can successfully call "findBorrowerX":
import BorrowerX
val br1 = BorrowerX(name = "Borrower1", maxBooks = 1)
val br2 = BorrowerX(name = "Borrower2", maxBooks = 2)
val br3 = BorrowerX(name = "Borrower3", maxBooks = 3)
val brs1 = arrayListOf(br1, br2, br3)
BorrowerX.findBorrowerX("Borrower1", brs1)
BorrowerX(name=Borrower1, maxBooks=1)
BorrowerX.findBorrowerX("Borrower-Bad", brs1)
null
But how do I make the call to "findBorrowerX2":
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX.getName(???))
And pass the iterated BorrowerX to getName??
This looks related, but I'm not sure:
Kotlin: how to pass a function as parameter to another?
Thank you in advance for your help with this!
EDIT:
Here is the equivalent Scala code for what I want to do:
def findBorrowerX2(n: String, brs: List[BorrowerX], f: BorrowerX => String): BorrowerX = {
val coll: List[BorrowerX] = brs.filter(f(_) == n)
if (coll.isEmpty) {
null
} else {
coll.head
}
}
scala> BorrowerX.findBorrowerX2("Borrower3", brs1, BorrowerX.getName(_))
res1: BorrowerX = BorrowerX(Borrower3,3)
scala> BorrowerX.findBorrowerX2("Borrower33", brs1, BorrowerX.getName(_))
res2: BorrowerX = null
Perhaps this is not possible in Kotlin?
You can use :: operator to get a function reference:
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX.Companion::getName)
Here BorrowerX.Companion::getName is a reference to the function getName declared in the companion object (named Companion) of the class BorrowerX. It has the type KFunction1<BorrowerX, String> which is a subtype of the required functional parameter type (BorrowerX) -> String.
It's worth noting that you can use :: operator to get a property reference too:
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX::name)
BorrowerX::name has the type KProperty1<BorrowerX, String> which also is a subtype of (BorrowerX) -> String. When invoked with the specified BorrowerX instance it returns the value of its name property.
As stated in the documentation on lambdas:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1, { it.name })
or when the lambda is the last parameter of the method:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1) { it.name }
Stating types and parameter names explicitly often improves readability:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1) { borrower:BorrowerX -> borrower.name }

Function returning ad-hoc object in Kotlin

Currently I have a private function which returns a Pair<User, User> object. The first user is the sender of something, the second user is the receiver of that thing.
I think this Pair<User, User> is not enough self explanatory - or clean if you like - even though it's just a private function.
Is it possible to return with an ad-hoc object like this:
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper): Any {
return object {
val sender = userCrud.findOne(instanceWrapper.fromWho)
val receiver = userCrud.findOne(instanceWrapper.toWho)
}
}
and use the returned value like this:
// ...
val users = findUsers(instanceWrapper)
users.sender // ...
users.receiver // ...
// ...
?
If not, what's the point of ad-hoc object in Kotlin?
Since the type can not be denoted in the language, use return type inference:
class Example {
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper) =
object {
val sender = userCrud.findOne(instanceWrapper.fromWho)
val receiver = userCrud.findOne(instanceWrapper.toWho)
}
fun foo() = findUsers(ExceptionInstanceWrapper()).sender
}
Another option would be to devise a data class:
class Example {
private data class Users(val sender: User, val receiver: User)
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper): Users {
return Users(
sender = userCrud.findOne(instanceWrapper.fromWho),
receiver = userCrud.findOne(instanceWrapper.toWho)
)
}
fun foo() = findUsers(ExceptionInstanceWrapper()).sender
}
Simply define your function as a lambda.
Here's simple object I've just written as an example in another context:
private val Map = {
val data = IntArray(400)
for (index in data.indices) {
data[index] = index * 3
}
object {
val get = { x: Int, y: Int ->
data[y * 20 + x]
}
}
}
fun main() {
val map = Map()
println(map.get(12,1))
}
Unfortunately, you cannot assign a type name, so it can be used as a return value but not as an argument. Maybe they'll make this possible so we can finally do OOP JS style.
Alternatively, they could implement object types equivalent to function types but that could end up being too wordy. You could then do a typedef but that would actually just be a kind of class definition 😅
Another option is to have a generic class for return types:
data class OutVal<T>(private var v: T?) {
fun set(newVal: T) {
v = newVal
}
fun get() = v
}
Usage example:
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper,
sender: OutVal<String>, receiver: OutVal<String>) {
sender.set(userCrud.findOne(instanceWrapper.fromWho))
receiver.set(userCrud.findOne(instanceWrapper.toWho))
}
val sender = OutVal("")
val receiver = OutVal("")
findUsers(instanceWrapper, sender, receiver)
sender.get() // ...
receiver.get() // ...