Skip to content

Commit c230b4f

Browse files
authored
Merge pull request #2262 from MarcMil/abstractsooffieldref
Cache field ref results
2 parents 03d1ee1 + 46fd839 commit c230b4f

4 files changed

Lines changed: 217 additions & 10 deletions

File tree

src/main/java/soot/AbstractSootFieldRef.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@
3838
public class AbstractSootFieldRef implements SootFieldRef {
3939
private static final Logger logger = LoggerFactory.getLogger(AbstractSootFieldRef.class);
4040

41+
private final SootClass declaringClass;
42+
private final String name;
43+
private final Type type;
44+
private final boolean isStatic;
45+
private SootField resolveCache = null;
46+
4147
public AbstractSootFieldRef(SootClass declaringClass, String name, Type type, boolean isStatic) {
42-
this.declaringClass = declaringClass;
43-
this.name = name;
44-
this.type = type;
45-
this.isStatic = isStatic;
4648
if (declaringClass == null) {
4749
throw new RuntimeException("Attempt to create SootFieldRef with null class");
4850
}
@@ -52,13 +54,12 @@ public AbstractSootFieldRef(SootClass declaringClass, String name, Type type, bo
5254
if (type == null) {
5355
throw new RuntimeException("Attempt to create SootFieldRef with null type");
5456
}
57+
this.declaringClass = declaringClass;
58+
this.name = name;
59+
this.type = type;
60+
this.isStatic = isStatic;
5561
}
5662

57-
private final SootClass declaringClass;
58-
private final String name;
59-
private final Type type;
60-
private final boolean isStatic;
61-
6263
@Override
6364
public SootClass declaringClass() {
6465
return declaringClass;
@@ -106,7 +107,13 @@ public String toString() {
106107

107108
@Override
108109
public SootField resolve() {
109-
return resolve(null);
110+
SootField cached = this.resolveCache;
111+
// Use the cached SootField if available and still valid
112+
if (cached == null || !cached.isValidResolve(this)) {
113+
cached = resolve(null);
114+
this.resolveCache = cached;
115+
}
116+
return cached;
110117
}
111118

112119
private SootField checkStatic(SootField ret) {

src/main/java/soot/SootField.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package soot;
22

3+
import java.util.Objects;
4+
35
/*-
46
* #%L
57
* Soot - a J*va Optimization Framework
@@ -240,4 +242,12 @@ public String getQuotedDeclaration() {
240242
public SootFieldRef makeRef() {
241243
return Scene.v().makeFieldRef(declaringClass, name, type, isStatic());
242244
}
245+
246+
public boolean isValidResolve(SootFieldRef f) {
247+
if (!isDeclared) {
248+
return false;
249+
}
250+
return (this.isStatic() == f.isStatic()) && Objects.equals(declaringClass, f.declaringClass())
251+
&& Objects.equals(name, f.name()) && Objects.equals(type, f.type());
252+
}
243253
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package soot;
2+
3+
/*-
4+
* #%L
5+
* Soot - a J*va Optimization Framework
6+
* %%
7+
* Copyright (C) 2021 Timothy Hoffman
8+
* %%
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as
11+
* published by the Free Software Foundation, either version 2.1 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Lesser Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Lesser Public
20+
* License along with this program. If not, see
21+
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
22+
* #L%
23+
*/
24+
25+
import java.util.Arrays;
26+
import java.util.stream.Collectors;
27+
28+
import org.junit.Assert;
29+
import org.junit.Test;
30+
import org.powermock.core.classloader.annotations.PowerMockIgnore;
31+
32+
import soot.jimple.Stmt;
33+
import soot.testing.framework.AbstractTestingFramework;
34+
35+
/**
36+
* @author Timothy Hoffman
37+
*/
38+
@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*" })
39+
public class AbstractSootFieldRefTest extends AbstractTestingFramework {
40+
41+
private static final String TEST_TARGET_CLASS = "soot.AbstractSootFieldRefTestInput";
42+
43+
@Override
44+
protected void setupSoot() {
45+
}
46+
47+
@Test
48+
public void testCachingInvalidation_Name() throws Exception {
49+
SootMethod m1 = prepareTarget(methodSigFromComponents(TEST_TARGET_CLASS, "void", "m1"), TEST_TARGET_CLASS);
50+
final SootClass clas = m1.getDeclaringClass();
51+
52+
// There is only 1 field in the class originally.
53+
Assert.assertEquals(Arrays.asList("f"),
54+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
55+
56+
// Ensure the previous value of AbstractSootFieldRef#resolveCache
57+
// is not used if the referenced field itself is modified.
58+
final Body b = m1.retrieveActiveBody();
59+
final SootFieldRef fRef = getFieldRef(b);
60+
Assert.assertEquals("f", fRef.name());
61+
62+
// Get the original referenced field appearing in the test source (i.e. "f")
63+
final SootField origF = fRef.resolve();
64+
Assert.assertTrue(!origF.isPhantom());
65+
Assert.assertEquals("f", origF.getName());
66+
67+
// Change the name of the method so the method reference no
68+
// longer refers to that method.
69+
origF.setName("newFieldName");
70+
Assert.assertEquals("newFieldName", origF.getName());
71+
72+
// Changing the field itself does not change the reference
73+
Assert.assertEquals("f", fRef.name());
74+
75+
// There is still just 1 field in the class (but "f" was renamed).
76+
Assert.assertEquals(Arrays.asList("newFieldName"),
77+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
78+
79+
// When resolving the reference, the cached value is not used since the
80+
// original field was renamed. It now gives a different field (that was
81+
// created automatically since a field with the name "f" no longer exists).
82+
final SootField newF = fRef.resolve();
83+
Assert.assertNotSame(origF, newF);
84+
Assert.assertEquals("f", newF.getName());
85+
86+
// There are now 2 fields since resolving "f" created it again.
87+
Assert.assertEquals(Arrays.asList("f", "newFieldName"),
88+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
89+
}
90+
91+
@Test
92+
public void testCachingInvalidation_Type() throws Exception {
93+
SootMethod m1 = prepareTarget(methodSigFromComponents(TEST_TARGET_CLASS, "void", "m1"), TEST_TARGET_CLASS);
94+
final SootClass clas = m1.getDeclaringClass();
95+
96+
// There is only 1 field in the class originally with boolean type.
97+
Assert.assertEquals(Arrays.asList("f"),
98+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
99+
Assert.assertEquals(Arrays.<Type>asList(BooleanType.v()),
100+
clas.getFields().stream().map(SootField::getType).sorted().collect(Collectors.toList()));
101+
102+
// Ensure the previous value of AbstractSootFieldRef#resolveCache
103+
// is not used if the referenced field itself is modified.
104+
final Body b = m1.retrieveActiveBody();
105+
final SootFieldRef fRef = getFieldRef(b);
106+
Assert.assertEquals("f", fRef.name());
107+
Assert.assertEquals(BooleanType.v(), fRef.type());
108+
109+
// Get the original referenced field appearing in the test source (i.e. "f")
110+
final SootField origF = fRef.resolve();
111+
Assert.assertTrue(!origF.isPhantom());
112+
Assert.assertEquals(BooleanType.v(), origF.getType());
113+
114+
// Change the type of the method so the method reference no
115+
// longer refers to that method.
116+
origF.setType(IntType.v());
117+
Assert.assertEquals(IntType.v(), origF.getType());
118+
119+
// Changing the field itself does not change the reference
120+
Assert.assertEquals(BooleanType.v(), fRef.type());
121+
122+
// There is still just 1 field in the class (but type of "f" was changed).
123+
Assert.assertEquals(Arrays.asList("f"),
124+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
125+
Assert.assertEquals(Arrays.<Type>asList(IntType.v()),
126+
clas.getFields().stream().map(SootField::getType).sorted().collect(Collectors.toList()));
127+
128+
// When resolving the reference, the cached value is not used since
129+
// the original field's type was changed. It now gives 'null'.
130+
final SootField newF = fRef.resolve();
131+
Assert.assertNotSame(origF, newF);
132+
Assert.assertNull(newF);
133+
134+
// There is still just 1 field in the class (but type of "f" was changed).
135+
Assert.assertEquals(Arrays.asList("f"),
136+
clas.getFields().stream().map(SootField::getName).sorted().collect(Collectors.toList()));
137+
Assert.assertEquals(Arrays.<Type>asList(IntType.v()),
138+
clas.getFields().stream().map(SootField::getType).sorted().collect(Collectors.toList()));
139+
}
140+
141+
private static SootFieldRef getFieldRef(Body b) {
142+
SootFieldRef retVal = null;
143+
UnitPatchingChain units = b.getUnits();
144+
for (Unit u : units) {
145+
Assert.assertTrue(u instanceof Stmt);
146+
Stmt s = (Stmt) u;
147+
if (s.containsFieldRef()) {
148+
Assert.assertNull(retVal);// the body has exactly 1 FieldRef
149+
retVal = s.getFieldRef().getFieldRef();
150+
}
151+
}
152+
Assert.assertNotNull(retVal);// the body has exactly 1 FieldRef
153+
return retVal;
154+
}
155+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package soot;
2+
3+
/*-
4+
* #%L
5+
* Soot - a J*va Optimization Framework
6+
* %%
7+
* Copyright (C) 2021 Timothy Hoffman
8+
* %%
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as
11+
* published by the Free Software Foundation, either version 2.1 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Lesser Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Lesser Public
20+
* License along with this program. If not, see
21+
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
22+
* #L%
23+
*/
24+
25+
/**
26+
* @author Timothy Hoffman
27+
*/
28+
public class AbstractSootFieldRefTestInput {
29+
30+
public boolean f;
31+
32+
public void m1() {
33+
this.f = true;
34+
}
35+
}

0 commit comments

Comments
 (0)