From 222e302b445c7ad2187ad9c69429db1b6d7e8d41 Mon Sep 17 00:00:00 2001 From: Timothy Hoffman <4001421+tim-hoffman@users.noreply.github.com> Date: Wed, 9 Jun 2021 17:33:41 -0500 Subject: [PATCH 1/3] Clarify error messages in FieldRefValidator --- .../jimple/validation/FieldRefValidator.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/soot/jimple/validation/FieldRefValidator.java b/src/main/java/soot/jimple/validation/FieldRefValidator.java index af3520c8a33..4c0ce8fb349 100644 --- a/src/main/java/soot/jimple/validation/FieldRefValidator.java +++ b/src/main/java/soot/jimple/validation/FieldRefValidator.java @@ -54,8 +54,8 @@ public void validate(Body body, List exceptions) { return; } - for (Unit unit : body.getUnits().getNonPatchingChain()) { - Stmt s = (Stmt) unit; + for (Unit u : body.getUnits().getNonPatchingChain()) { + Stmt s = (Stmt) u; if (!s.containsFieldRef()) { continue; } @@ -66,13 +66,12 @@ public void validate(Body body, List exceptions) { try { SootField field = v.getField(); if (field == null) { - exceptions.add(new UnitValidationException(unit, body, "Resolved field is null: " + fr.toString())); + exceptions.add(new UnitValidationException(u, body, "Resolved field is null: " + fr.toString())); } else if (!field.isStatic() && !field.isPhantom()) { - exceptions - .add(new UnitValidationException(unit, body, "Trying to get a static field which is non-static: " + v)); + exceptions.add(new UnitValidationException(u, body, "Used StaticFieldRef for a non-static field: " + v)); } } catch (ResolutionFailedException e) { - exceptions.add(new UnitValidationException(unit, body, "Trying to get a static field which is non-static: " + v)); + exceptions.add(new UnitValidationException(u, body, "Unable to resolve FieldRef " + v + ": " + e.getMessage())); } } else if (fr instanceof InstanceFieldRef) { InstanceFieldRef v = (InstanceFieldRef) fr; @@ -80,12 +79,12 @@ public void validate(Body body, List exceptions) { try { SootField field = v.getField(); if (field == null) { - exceptions.add(new UnitValidationException(unit, body, "Resolved field is null: " + fr.toString())); + exceptions.add(new UnitValidationException(u, body, "Resolved field is null: " + fr.toString())); } else if (field.isStatic() && !field.isPhantom()) { - exceptions.add(new UnitValidationException(unit, body, "Trying to get an instance field which is static: " + v)); + exceptions.add(new UnitValidationException(u, body, "Used InstanceFieldRef for a static field: " + v)); } } catch (ResolutionFailedException e) { - exceptions.add(new UnitValidationException(unit, body, "Trying to get an instance field which is static: " + v)); + exceptions.add(new UnitValidationException(u, body, "Unable to resolve FieldRef " + v + ": " + e.getMessage())); } } else { throw new AssertionError("unknown field ref: " + fr); From 30295540359d0c43119193d301372f66d7c36947 Mon Sep 17 00:00:00 2001 From: Timothy Hoffman <4001421+tim-hoffman@users.noreply.github.com> Date: Mon, 28 Jun 2021 22:38:28 -0500 Subject: [PATCH 2/3] Update FieldRefValidator to handle BafBody as well --- .../jimple/validation/FieldRefValidator.java | 129 +++++++++++++----- 1 file changed, 98 insertions(+), 31 deletions(-) diff --git a/src/main/java/soot/jimple/validation/FieldRefValidator.java b/src/main/java/soot/jimple/validation/FieldRefValidator.java index 4c0ce8fb349..103d9c0a1b9 100644 --- a/src/main/java/soot/jimple/validation/FieldRefValidator.java +++ b/src/main/java/soot/jimple/validation/FieldRefValidator.java @@ -27,8 +27,16 @@ import soot.Body; import soot.ResolutionFailedException; import soot.SootField; +import soot.SootFieldRef; import soot.SootMethod; import soot.Unit; +import soot.baf.BafBody; +import soot.baf.FieldArgInst; +import soot.baf.FieldGetInst; +import soot.baf.FieldPutInst; +import soot.baf.Inst; +import soot.baf.StaticGetInst; +import soot.baf.StaticPutInst; import soot.jimple.FieldRef; import soot.jimple.InstanceFieldRef; import soot.jimple.StaticFieldRef; @@ -37,6 +45,10 @@ import soot.validation.UnitValidationException; import soot.validation.ValidationException; +/** + * @author Alexandre Bartel + * @author Timothy Hoffman + */ public enum FieldRefValidator implements BodyValidator { INSTANCE; @@ -49,45 +61,100 @@ public static FieldRefValidator v() { */ @Override public void validate(Body body, List exceptions) { - SootMethod method = body.getMethod(); + final SootMethod method = body.getMethod(); if (method.isAbstract()) { return; } - for (Unit u : body.getUnits().getNonPatchingChain()) { - Stmt s = (Stmt) u; - if (!s.containsFieldRef()) { - continue; - } - FieldRef fr = s.getFieldRef(); - - if (fr instanceof StaticFieldRef) { - StaticFieldRef v = (StaticFieldRef) fr; - try { - SootField field = v.getField(); - if (field == null) { - exceptions.add(new UnitValidationException(u, body, "Resolved field is null: " + fr.toString())); - } else if (!field.isStatic() && !field.isPhantom()) { - exceptions.add(new UnitValidationException(u, body, "Used StaticFieldRef for a non-static field: " + v)); + final ValidatorImpl vi; + if (body instanceof BafBody) { + vi = new ValidatorImpl(body, exceptions) { + @Override + public void check(Unit unit) { + Inst inst = (Inst) unit; + if (inst.containsFieldRef()) { + assert (inst instanceof FieldArgInst); // interface that defines getFieldRef() + check(unit, ((FieldArgInst) unit).getFieldRef(), inst); } - } catch (ResolutionFailedException e) { - exceptions.add(new UnitValidationException(u, body, "Unable to resolve FieldRef " + v + ": " + e.getMessage())); } - } else if (fr instanceof InstanceFieldRef) { - InstanceFieldRef v = (InstanceFieldRef) fr; - - try { - SootField field = v.getField(); - if (field == null) { - exceptions.add(new UnitValidationException(u, body, "Resolved field is null: " + fr.toString())); - } else if (field.isStatic() && !field.isPhantom()) { - exceptions.add(new UnitValidationException(u, body, "Used InstanceFieldRef for a static field: " + v)); + + @Override + protected boolean isValidStaticRef(Inst refToCheck) { + return (refToCheck instanceof StaticPutInst) || (refToCheck instanceof StaticGetInst); + } + + @Override + protected boolean isValidNonStaticRef(Inst refToCheck) { + return (refToCheck instanceof FieldPutInst) || (refToCheck instanceof FieldGetInst); + } + }; + } else { + vi = new ValidatorImpl(body, exceptions) { + @Override + public void check(Unit unit) { + Stmt stmt = (Stmt) unit; + if (stmt.containsFieldRef()) { + FieldRef ref = stmt.getFieldRef(); + check(unit, ref.getFieldRef(), ref); + } + } + + @Override + protected boolean isValidStaticRef(FieldRef refToCheck) { + return (refToCheck instanceof StaticFieldRef); + } + + @Override + protected boolean isValidNonStaticRef(FieldRef refToCheck) { + return (refToCheck instanceof InstanceFieldRef); + } + }; + } + + // Run the validator on all units in the body + for (Unit unit : body.getUnits().getNonPatchingChain()) { + vi.check(unit); + } + } + + private static abstract class ValidatorImpl { + + private final Body body; + private final List exs; + + public ValidatorImpl(Body body, List exceptions) { + this.body = body; + this.exs = exceptions; + } + + public abstract void check(Unit unit); + + protected abstract boolean isValidStaticRef(T refToCheck); + + protected abstract boolean isValidNonStaticRef(T refToCheck); + + protected final void check(Unit unit, SootFieldRef sRef, T refToCheck) { + SootField field; + try { + field = sRef.resolve(); + } catch (ResolutionFailedException e) { + exs.add(new UnitValidationException(unit, body, "Unable to resolve SootFieldRef " + sRef + ": " + e.getMessage())); + field = null; + } + if (field == null) { + exs.add(new UnitValidationException(unit, body, "SootFieldRef resolved to null: " + sRef)); + } else if (!field.isPhantom()) { + if (field.isStatic()) { + if (!isValidStaticRef(refToCheck)) { + String s = refToCheck.getClass().getName(); + exs.add(new UnitValidationException(unit, body, "Used " + s + " for static field " + sRef)); + } + } else { + if (!isValidNonStaticRef(refToCheck)) { + String s = refToCheck.getClass().getName(); + exs.add(new UnitValidationException(unit, body, "Used " + s + " for non-static field " + sRef)); } - } catch (ResolutionFailedException e) { - exceptions.add(new UnitValidationException(u, body, "Unable to resolve FieldRef " + v + ": " + e.getMessage())); } - } else { - throw new AssertionError("unknown field ref: " + fr); } } } From a69bbef4902d9bac25ea1d7f0ae4400333fc747d Mon Sep 17 00:00:00 2001 From: Timothy Hoffman <4001421+tim-hoffman@users.noreply.github.com> Date: Tue, 6 Jul 2021 17:14:19 -0500 Subject: [PATCH 3/3] Add FieldRefValidatorTest --- .../validation/FieldRefValidatorTest.java | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 src/test/java/soot/jimple/validation/FieldRefValidatorTest.java diff --git a/src/test/java/soot/jimple/validation/FieldRefValidatorTest.java b/src/test/java/soot/jimple/validation/FieldRefValidatorTest.java new file mode 100644 index 00000000000..39c43df930e --- /dev/null +++ b/src/test/java/soot/jimple/validation/FieldRefValidatorTest.java @@ -0,0 +1,344 @@ +package soot.jimple.validation; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2021 Timothy Hoffman + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import soot.Body; +import soot.G; +import soot.IntType; +import soot.Local; +import soot.Modifier; +import soot.Scene; +import soot.SootClass; +import soot.SootField; +import soot.SootFieldRef; +import soot.SootMethod; +import soot.Type; +import soot.Unit; +import soot.UnitPatchingChain; +import soot.Value; +import soot.VoidType; +import soot.baf.BafBody; +import soot.baf.Inst; +import soot.jimple.Jimple; +import soot.jimple.JimpleBody; +import soot.jimple.Stmt; +import soot.options.Options; +import soot.util.Chain; +import soot.validation.ValidationException; + +/** + * + * @author Timothy Hoffman + */ +public class FieldRefValidatorTest { + + public FieldRefValidatorTest() { + } + + @Before + public void setUp() { + G.reset(); + + final Options opts = Options.v(); + opts.set_allow_phantom_refs(false); + opts.set_no_bodies_for_excluded(true); + + // Disable "bb" phase for direct Jimple->Baf translation + opts.setPhaseOption("bb", "enabled:false"); + + final Scene scene = Scene.v(); + scene.loadNecessaryClasses(); + + // Ensure that phantom references are not allowed + Assert.assertFalse(scene.allowsPhantomRefs()); + } + + private static final String DUMMY_FIELD_NAME_STAT = "fStat"; + private static final String DUMMY_FIELD_NAME_INST = "fInst"; + + private static class TestBodyGenerator { + // Important: the fields in this class must NOT be static since G is reset for every test. + + final Scene scene; + final Jimple jimp; + final Type fieldType; + final SootClass fieldClass; + final JimpleBody body; + final UnitPatchingChain units; + final Local fieldLocal; + final Local objLocal; + + public TestBodyGenerator() { + this.scene = Scene.v(); + this.jimp = Jimple.v(); + this.fieldType = IntType.v(); + this.fieldClass = generateClassWithFields(this.scene, this.fieldType); + this.body = generateEmptyMethodBody(this.jimp); + this.units = this.body.getUnits(); + + // Create a Local with the same type as the field references and another + // with the type of the class containing the fields (to use as the base). + final Chain locals = this.body.getLocals(); + this.fieldLocal = this.jimp.newLocal("a", this.fieldType); + locals.add(this.fieldLocal); + this.objLocal = this.jimp.newLocal("b", this.fieldClass.getType()); + locals.add(this.objLocal); + } + + /** + * Generate a new {@link SootClass} containing a static field and a instance field (both with type {@link #fieldType}) + * and add that class to the {@link Scene} as an application class. + * + * @param scene + * + * @return + */ + private static SootClass generateClassWithFields(Scene scene, Type fieldType) { + SootClass c = new SootClass("DummyClass"); + + c.addField(new SootField(DUMMY_FIELD_NAME_STAT, fieldType, Modifier.STATIC)); + c.addField(new SootField(DUMMY_FIELD_NAME_INST, fieldType, 0)); + + scene.addClass(c); + c.setApplicationClass(); + + return c; + } + + /** + * Create a {@link JimpleBody} and a {@link SootMethod} that is static, has no parameters, and has void return type to + * simplify later construction of the {@link JimpleBody} (i.e. no @this or @param IdentityRef are needed). The + * {@link SootMethod} does not have a declaring class set. + * + * @return + */ + private static JimpleBody generateEmptyMethodBody(Jimple jimp) { + final SootMethod m = new SootMethod("m", Collections.emptyList(), VoidType.v(), Modifier.STATIC); + Assert.assertTrue(m.isStatic()); + Assert.assertFalse(m.isAbstract()); + + JimpleBody body = jimp.newBody(m); + m.setActiveBody(body); + + return body; + } + + public SootFieldRef makeValidRefToFieldNameStat() { + return scene.makeFieldRef(fieldClass, DUMMY_FIELD_NAME_STAT, fieldType, true); + } + + public SootFieldRef makeValidRefToFieldNameInst() { + return scene.makeFieldRef(fieldClass, DUMMY_FIELD_NAME_INST, fieldType, false); + } + + // Invalid because the SootFieldRef is non-static although field is static + public SootFieldRef makeInvalidRefToFieldNameStat() { + return scene.makeFieldRef(fieldClass, DUMMY_FIELD_NAME_STAT, fieldType, false); + } + + // Invalid because the SootFieldRef is static although field is non-static + public SootFieldRef makeInvalidRefToFieldNameInst() { + return scene.makeFieldRef(fieldClass, DUMMY_FIELD_NAME_INST, fieldType, true); + } + + public void generateAssign(Value lhs, Value rhs) { + units.add(jimp.newAssignStmt(lhs, rhs)); + } + } + + /** + * Run {@link FieldRefValidator} and return a list containing any {@link ValidationException} that it generated. + * + * @param body + * + * @return + */ + private static List runValidator(Body body) { + List exceptions = new ArrayList<>(); + FieldRefValidator.INSTANCE.validate(body, exceptions); + return exceptions; + } + + /** + * Count the number of {@link Stmt} or {@link Inst} in the given {@link Body} that contain a field reference. + * + * @param body + * @return + */ + private static int countFieldReferences(Body body) { + int count = 0; + for (Unit u : body.getUnits()) { + if (u instanceof Stmt) { + if (((Stmt) u).containsFieldRef()) { + count++; + } + } else if (u instanceof Inst) { + if (((Inst) u).containsFieldRef()) { + count++; + } + } + } + return count; + } + + private static void testValid(Function converter) { + TestBodyGenerator g = new TestBodyGenerator(); + + // Generate a valid RHS static field reference + g.generateAssign(g.fieldLocal, g.jimp.newStaticFieldRef(g.makeValidRefToFieldNameStat())); + + // Generate a valid LHS static field reference + g.generateAssign(g.jimp.newStaticFieldRef(g.makeValidRefToFieldNameStat()), g.fieldLocal); + + // Generate a valid RHS instance field reference + g.generateAssign(g.fieldLocal, g.jimp.newInstanceFieldRef(g.objLocal, g.makeValidRefToFieldNameInst())); + + // Generate a valid LHS instance field reference + g.generateAssign(g.jimp.newInstanceFieldRef(g.objLocal, g.makeValidRefToFieldNameInst()), g.fieldLocal); + + final Body b = converter.apply(g); + + // Ensure all of the expected field references appear in the Body + Assert.assertEquals(4, countFieldReferences(b)); + + // Run the validator and ensure there are no ValidationExceptions + Assert.assertTrue(runValidator(b).isEmpty()); + } + + private static final Function JIMP = g -> g.body; + private static final Function BAF = g -> new BafBody(g.body, Collections.emptyMap()); + + @Test + public void testJimpleValid() { + testValid(JIMP); + } + + @Test + public void testBafValid() { + testValid(BAF); + } + + // TESTING: LHS InstanceFieldRef used on static field + private static void testInvalid_LIS(Function converter) { + TestBodyGenerator g = new TestBodyGenerator(); + g.generateAssign(g.jimp.newInstanceFieldRef(g.objLocal, g.makeInvalidRefToFieldNameStat()), g.fieldLocal); + + final Body b = converter.apply(g); + + // Ensure all of the expected field references appear in the Body + Assert.assertEquals(1, countFieldReferences(b)); + + // Run the validator and ensure there is at least one ValidationException + Assert.assertFalse(runValidator(b).isEmpty()); + } + + @Test + public void testJimpleInvalid_LIS() { + testInvalid_LIS(JIMP); + } + + @Test + public void testBafInvalid_LIS() { + testInvalid_LIS(BAF); + } + + // TESTING: RHS InstanceFieldRef used on static field + private static void testInvalid_RIS(Function converter) { + TestBodyGenerator g = new TestBodyGenerator(); + g.generateAssign(g.fieldLocal, g.jimp.newInstanceFieldRef(g.objLocal, g.makeInvalidRefToFieldNameStat())); + + final Body b = converter.apply(g); + + // Ensure all of the expected field references appear in the Body + Assert.assertEquals(1, countFieldReferences(b)); + + // Run the validator and ensure there is at least one ValidationException + Assert.assertFalse(runValidator(b).isEmpty()); + } + + @Test + public void testJimpleInvalid_RIS() { + testInvalid_RIS(JIMP); + } + + @Test + public void testBafInvalid_RIS() { + testInvalid_RIS(BAF); + } + + // TESTING: LHS StaticFieldRef used on instance field + private static void testInvalid_LSI(Function converter) { + TestBodyGenerator g = new TestBodyGenerator(); + g.generateAssign(g.jimp.newStaticFieldRef(g.makeInvalidRefToFieldNameInst()), g.fieldLocal); + + final Body b = converter.apply(g); + + // Ensure all of the expected field references appear in the Body + Assert.assertEquals(1, countFieldReferences(b)); + + // Run the validator and ensure there is at least one ValidationException + Assert.assertFalse(runValidator(b).isEmpty()); + } + + @Test + public void testJimpleInvalid_LSI() { + testInvalid_LSI(JIMP); + } + + @Test + public void testBafInvalid_LSI() { + testInvalid_LSI(BAF); + } + + // TESTING: RHS StaticFieldRef used on instance field + private static void testInvalid_RSI(Function converter) { + TestBodyGenerator g = new TestBodyGenerator(); + g.generateAssign(g.fieldLocal, g.jimp.newStaticFieldRef(g.makeInvalidRefToFieldNameInst())); + + final Body b = converter.apply(g); + + // Ensure all of the expected field references appear in the Body + Assert.assertEquals(1, countFieldReferences(b)); + + // Run the validator and ensure there is at least one ValidationException + Assert.assertFalse(runValidator(b).isEmpty()); + } + + @Test + public void testJimpleInvalid_RSI() { + testInvalid_RSI(JIMP); + } + + @Test + public void testBafInvalid_RSI() { + testInvalid_RSI(BAF); + } +}