diff --git a/com.ibm.wala.cast.python.ml.test/source/com/ibm/wala/cast/python/ml/test/TestTensorflow2Model.java b/com.ibm.wala.cast.python.ml.test/source/com/ibm/wala/cast/python/ml/test/TestTensorflow2Model.java
index bf9d86cb..a4f01cdb 100644
--- a/com.ibm.wala.cast.python.ml.test/source/com/ibm/wala/cast/python/ml/test/TestTensorflow2Model.java
+++ b/com.ibm.wala.cast.python.ml.test/source/com/ibm/wala/cast/python/ml/test/TestTensorflow2Model.java
@@ -1204,6 +1204,41 @@ public void testModelCall4()
test("tf2_test_model_call4.py", "SequentialModel.__call__", 1, 1, 3);
}
+ /**
+ * Test call string imprecision as described in
+ * https://github.com/wala/WALA/discussions/1417#discussioncomment-10085680. This should fail due
+ * to https://github.com/wala/ML/issues/207.
+ */
+ @Test(expected = java.lang.AssertionError.class)
+ public void testModelCall5()
+ throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException {
+ test(
+ new String[] {
+ "proj66/src/tf2_test_model_call5b.py",
+ "proj66/tf2_test_model_call5.py",
+ "proj66/tf2_test_model_call5a.py"
+ },
+ "tf2_test_model_call5.py",
+ "SequentialModel.__call__",
+ "proj66",
+ 1,
+ 1,
+ 3);
+
+ test(
+ new String[] {
+ "proj66/src/tf2_test_model_call5b.py",
+ "proj66/tf2_test_model_call5.py",
+ "proj66/tf2_test_model_call5a.py"
+ },
+ "tf2_test_model_call5a.py",
+ "SequentialModel.__call__",
+ "proj66",
+ 1,
+ 1,
+ 3);
+ }
+
@Test
public void testModelAttributes()
throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException {
diff --git a/com.ibm.wala.cast.python.test/.pydevproject b/com.ibm.wala.cast.python.test/.pydevproject
index bd6807b9..654655ea 100644
--- a/com.ibm.wala.cast.python.test/.pydevproject
+++ b/com.ibm.wala.cast.python.test/.pydevproject
@@ -19,6 +19,7 @@
/${PROJECT_DIR_NAME}/data/proj35
/${PROJECT_DIR_NAME}/data/proj37
/${PROJECT_DIR_NAME}/data/proj45
+ /${PROJECT_DIR_NAME}/data/proj66
diff --git a/com.ibm.wala.cast.python.test/data/proj66/src/__init__.py b/com.ibm.wala.cast.python.test/data/proj66/src/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/com.ibm.wala.cast.python.test/data/proj66/src/__init__.py
@@ -0,0 +1 @@
+
diff --git a/com.ibm.wala.cast.python.test/data/proj66/src/tf2_test_model_call5b.py b/com.ibm.wala.cast.python.test/data/proj66/src/tf2_test_model_call5b.py
new file mode 100644
index 00000000..59bea179
--- /dev/null
+++ b/com.ibm.wala.cast.python.test/data/proj66/src/tf2_test_model_call5b.py
@@ -0,0 +1,9 @@
+# Test https://github.com/wala/WALA/discussions/1417#discussioncomment-10085680.
+
+
+def f(m, d):
+ return m.predict(d)
+
+
+def g(m, d):
+ return f(m, d)
diff --git a/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5.py b/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5.py
new file mode 100644
index 00000000..64274e90
--- /dev/null
+++ b/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5.py
@@ -0,0 +1,44 @@
+# Test https://github.com/wala/WALA/discussions/1417#discussioncomment-10085680.
+
+import tensorflow as tf
+from src.tf2_test_model_call5b import g
+
+# Create an override model to classify pictures
+
+
+class SequentialModel(tf.keras.Model):
+
+ def __init__(self, **kwargs):
+ super(SequentialModel, self).__init__(**kwargs)
+
+ self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
+
+ # Add a lot of small layers
+ num_layers = 100
+ self.my_layers = [
+ tf.keras.layers.Dense(64, activation="relu") for n in range(num_layers)
+ ]
+
+ self.dropout = tf.keras.layers.Dropout(0.2)
+ self.dense_2 = tf.keras.layers.Dense(10)
+
+ def __call__(self, x):
+ print("Raffi 1")
+ x = self.flatten(x)
+
+ for layer in self.my_layers:
+ x = layer(x)
+
+ x = self.dropout(x)
+ x = self.dense_2(x)
+
+ return x
+
+ def predict(self, x):
+ return self(x)
+
+
+input_data = tf.random.uniform([20, 28, 28])
+
+model = SequentialModel()
+result = g(model, input_data)
diff --git a/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5a.py b/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5a.py
new file mode 100644
index 00000000..842edd5b
--- /dev/null
+++ b/com.ibm.wala.cast.python.test/data/proj66/tf2_test_model_call5a.py
@@ -0,0 +1,44 @@
+# Test https://github.com/wala/WALA/discussions/1417#discussioncomment-10085680.
+
+import tensorflow as tf
+from src.tf2_test_model_call5b import g
+
+# Create an override model to classify pictures
+
+
+class SequentialModel(tf.keras.Model):
+
+ def __init__(self, **kwargs):
+ super(SequentialModel, self).__init__(**kwargs)
+
+ self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
+
+ # Add a lot of small layers
+ num_layers = 100
+ self.my_layers = [
+ tf.keras.layers.Dense(64, activation="relu") for n in range(num_layers)
+ ]
+
+ self.dropout = tf.keras.layers.Dropout(0.2)
+ self.dense_2 = tf.keras.layers.Dense(10)
+
+ def __call__(self, x):
+ print("Raffi 2")
+ x = self.flatten(x)
+
+ for layer in self.my_layers:
+ x = layer(x)
+
+ x = self.dropout(x)
+ x = self.dense_2(x)
+
+ return x
+
+ def predict(self, x):
+ return self(x)
+
+
+input_data = tf.random.uniform([20, 28, 28])
+
+model = SequentialModel()
+result = g(model, input_data)
diff --git a/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ipa/callgraph/PythonInstanceMethodTrampolineTargetSelector.java b/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ipa/callgraph/PythonInstanceMethodTrampolineTargetSelector.java
index 2b9e85ab..53c3695c 100644
--- a/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ipa/callgraph/PythonInstanceMethodTrampolineTargetSelector.java
+++ b/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ipa/callgraph/PythonInstanceMethodTrampolineTargetSelector.java
@@ -43,6 +43,7 @@
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.intset.OrdinalSet;
+import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
@@ -223,6 +224,8 @@ private IClass getCallable(CGNode caller, IClassHierarchy cha, PythonInvokeInstr
PointerKey receiver = pkf.getPointerKeyForLocal(caller, call.getUse(0));
OrdinalSet objs = builder.getPointerAnalysis().getPointsToSet(receiver);
+ Map instanceToCallable = new HashMap<>();
+
for (InstanceKey o : objs) {
AllocationSiteInNode instanceKey = getAllocationSiteInNode(o);
if (instanceKey != null) {
@@ -254,10 +257,29 @@ private IClass getCallable(CGNode caller, IClassHierarchy cha, PythonInvokeInstr
LOGGER.info("Applying callable workaround for https://github.com/wala/ML/issues/118.");
}
- if (callable != null) return callable;
+ if (callable != null) {
+ if (instanceToCallable.containsKey(instanceKey))
+ throw new IllegalStateException("Exisitng mapping found for: " + instanceKey);
+
+ IClass previousValue = instanceToCallable.put(instanceKey, callable);
+ assert previousValue == null : "Not expecting a previous mapping.";
+ }
}
}
+ // if there's only one possible option.
+ if (instanceToCallable.values().size() == 1) {
+ IClass callable = instanceToCallable.values().iterator().next();
+ assert callable != null : "Callable should be non-null.";
+ return callable;
+ }
+
+ // if we have multiple candidates.
+ if (instanceToCallable.values().size() > 1)
+ // we cannot accurately select one.
+ LOGGER.warning(
+ "Multiple (" + instanceToCallable.values().size() + ") callable targets found.");
+
return null;
}