diff --git a/src/main/java/soot/jimple/toolkits/scalar/CopyPropagator.java b/src/main/java/soot/jimple/toolkits/scalar/CopyPropagator.java index 2b067a2494c..4810bb39cf4 100644 --- a/src/main/java/soot/jimple/toolkits/scalar/CopyPropagator.java +++ b/src/main/java/soot/jimple/toolkits/scalar/CopyPropagator.java @@ -57,7 +57,7 @@ import soot.tagkit.SourceLnPosTag; import soot.tagkit.Tag; import soot.toolkits.exceptions.ThrowAnalysis; -import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.toolkits.graph.PseudoTopologicalOrderer; import soot.toolkits.graph.UnitGraph; import soot.toolkits.scalar.LocalDefs; @@ -65,19 +65,20 @@ public class CopyPropagator extends BodyTransformer { private static final Logger logger = LoggerFactory.getLogger(CopyPropagator.class); - protected ThrowAnalysis throwAnalysis = null; - protected boolean forceOmitExceptingUnitEdges = false; + protected final ThrowAnalysis throwAnalysis; + protected final boolean forceOmitExceptingUnitEdges; public CopyPropagator(Singletons.Global g) { + this(null, false); } public CopyPropagator(ThrowAnalysis ta) { - this.throwAnalysis = ta; + this(ta, false); } public CopyPropagator(ThrowAnalysis ta, boolean forceOmitExceptingUnitEdges) { this.throwAnalysis = ta; - this.forceOmitExceptingUnitEdges = forceOmitExceptingUnitEdges; + this.forceOmitExceptingUnitEdges = forceOmitExceptingUnitEdges ? true : Options.v().omit_excepting_unit_edges(); } public static CopyPropagator v() { @@ -104,7 +105,6 @@ protected void internalTransform(Body b, String phaseName, Map o if (Options.v().verbose()) { logger.debug("[" + b.getMethod().getName() + "] Propagating copies..."); } - if (Options.v().time()) { Timers.v().propagatorTimer.start(); } @@ -123,149 +123,144 @@ protected void internalTransform(Body b, String phaseName, Map o } } - if (throwAnalysis == null) { - throwAnalysis = Scene.v().getDefaultThrowAnalysis(); - } + // Go through the definitions, building the webs + int fastCopyPropagationCount = 0; + int slowCopyPropagationCount = 0; - if (!forceOmitExceptingUnitEdges) { - forceOmitExceptingUnitEdges = Options.v().omit_excepting_unit_edges(); + ThrowAnalysis ta = this.throwAnalysis; + if (ta == null) { + // NOTE: the CopyPropagator constructor should not call "Scene.v()" + // thus, this condition check must remain here. + ta = Scene.v().getDefaultThrowAnalysis(); } - - { - // Go through the definitions, building the webs - int fastCopyPropagationCount = 0; - int slowCopyPropagationCount = 0; - - UnitGraph graph = new ExceptionalUnitGraph(b, throwAnalysis, forceOmitExceptingUnitEdges); - LocalDefs localDefs = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(graph); - CPOptions options = new CPOptions(opts); - // Perform a local propagation pass. - for (Unit u : (new PseudoTopologicalOrderer()).newList(graph, false)) { - for (ValueBox useBox : u.getUseBoxes()) { - Value value = useBox.getValue(); - if (value instanceof Local) { - Local l = (Local) value; - - // We force propagating nulls. If a target can only be - // null due to typing, we always inline that constant. - if (!(l.getType() instanceof NullType)) { - if (options.only_regular_locals() && l.getName().startsWith("$")) { - continue; - } - if (options.only_stack_locals() && !l.getName().startsWith("$")) { - continue; - } + UnitGraph graph = new FullExceptionalUnitGraph(b, ta, forceOmitExceptingUnitEdges); + LocalDefs localDefs = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(graph); + CPOptions options = new CPOptions(opts); + // Perform a local propagation pass. + for (Unit u : (new PseudoTopologicalOrderer()).newList(graph, false)) { + for (ValueBox useBox : u.getUseBoxes()) { + Value value = useBox.getValue(); + if (value instanceof Local) { + Local l = (Local) value; + + // We force propagating nulls. If a target can only be + // null due to typing, we always inline that constant. + if (!(l.getType() instanceof NullType)) { + if (options.only_regular_locals() && l.getName().startsWith("$")) { + continue; } + if (options.only_stack_locals() && !l.getName().startsWith("$")) { + continue; + } + } - // We can propagate the definition if we either only have one definition - // or all definitions are side-effect free and equal. For starters, we - // only support constants in the case of multiple definitions. - List defsOfUse = localDefs.getDefsOfAt(l, u); - boolean propagateDef = defsOfUse.size() == 1; - if (!propagateDef && defsOfUse.size() > 0) { - boolean agrees = true; - Constant constVal = null; - for (Unit defUnit : defsOfUse) { - boolean defAgrees = false; - if (defUnit instanceof AssignStmt) { - Value rightOp = ((AssignStmt) defUnit).getRightOp(); - if (rightOp instanceof Constant) { - if (constVal == null) { - constVal = (Constant) rightOp; - defAgrees = true; - } else if (constVal.equals(rightOp)) { - defAgrees = true; - } + // We can propagate the definition if we either only have one definition + // or all definitions are side-effect free and equal. For starters, we + // only support constants in the case of multiple definitions. + List defsOfUse = localDefs.getDefsOfAt(l, u); + boolean propagateDef = defsOfUse.size() == 1; + if (!propagateDef && defsOfUse.size() > 0) { + boolean agrees = true; + Constant constVal = null; + for (Unit defUnit : defsOfUse) { + boolean defAgrees = false; + if (defUnit instanceof AssignStmt) { + Value rightOp = ((AssignStmt) defUnit).getRightOp(); + if (rightOp instanceof Constant) { + if (constVal == null) { + constVal = (Constant) rightOp; + defAgrees = true; + } else if (constVal.equals(rightOp)) { + defAgrees = true; } } - agrees &= defAgrees; } - propagateDef = agrees; + agrees &= defAgrees; } + propagateDef = agrees; + } - if (propagateDef) { - final DefinitionStmt def = (DefinitionStmt) defsOfUse.get(0); - final Value rightOp = def.getRightOp(); + if (propagateDef) { + final DefinitionStmt def = (DefinitionStmt) defsOfUse.get(0); + final Value rightOp = def.getRightOp(); - if (rightOp instanceof Constant) { - if (useBox.canContainValue(rightOp)) { - useBox.setValue(rightOp); - copyLineTags(useBox, def); - } - } else if (rightOp instanceof CastExpr) { - CastExpr ce = (CastExpr) rightOp; - if (ce.getCastType() instanceof RefLikeType) { - Value op = ce.getOp(); - if ((op instanceof IntConstant && ((IntConstant) op).value == 0) - || (op instanceof LongConstant && ((LongConstant) op).value == 0)) { - if (useBox.canContainValue(NullConstant.v())) { - useBox.setValue(NullConstant.v()); - copyLineTags(useBox, def); - } - } - } - } else if (rightOp instanceof Local) { - Local m = (Local) rightOp; - if (l != m) { - Integer defCount = localToDefCount.get(m); - if (defCount == null || defCount == 0) { - throw new RuntimeException("Variable " + m + " used without definition!"); - } else if (defCount == 1) { - useBox.setValue(m); + if (rightOp instanceof Constant) { + if (useBox.canContainValue(rightOp)) { + useBox.setValue(rightOp); + copyLineTags(useBox, def); + } + } else if (rightOp instanceof CastExpr) { + CastExpr ce = (CastExpr) rightOp; + if (ce.getCastType() instanceof RefLikeType) { + Value op = ce.getOp(); + if ((op instanceof IntConstant && ((IntConstant) op).value == 0) + || (op instanceof LongConstant && ((LongConstant) op).value == 0)) { + if (useBox.canContainValue(NullConstant.v())) { + useBox.setValue(NullConstant.v()); copyLineTags(useBox, def); - fastCopyPropagationCount++; - continue; } + } + } + } else if (rightOp instanceof Local) { + Local m = (Local) rightOp; + if (l != m) { + Integer defCount = localToDefCount.get(m); + if (defCount == null || defCount == 0) { + throw new RuntimeException("Variable " + m + " used without definition!"); + } else if (defCount == 1) { + useBox.setValue(m); + copyLineTags(useBox, def); + fastCopyPropagationCount++; + continue; + } - List path = graph.getExtendedBasicBlockPathBetween(def, u); - if (path == null) { - // no path in the extended basic block - continue; - } + List path = graph.getExtendedBasicBlockPathBetween(def, u); + if (path == null) { + // no path in the extended basic block + continue; + } - { - boolean isRedefined = false; + { + boolean isRedefined = false; - Iterator pathIt = path.iterator(); - // Skip first node - pathIt.next(); - // Make sure that m is not redefined along path - while (pathIt.hasNext()) { - Stmt s = (Stmt) pathIt.next(); + Iterator pathIt = path.iterator(); + // Skip first node + pathIt.next(); + // Make sure that m is not redefined along path + while (pathIt.hasNext()) { + Stmt s = (Stmt) pathIt.next(); - if (u == s) { - // Don't look at the last statement - // since it is evaluated after the uses. + if (u == s) { + // Don't look at the last statement + // since it is evaluated after the uses. + break; + } + if (s instanceof DefinitionStmt) { + if (((DefinitionStmt) s).getLeftOp() == m) { + isRedefined = true; break; } - if (s instanceof DefinitionStmt) { - if (((DefinitionStmt) s).getLeftOp() == m) { - isRedefined = true; - break; - } - } - } - - if (isRedefined) { - continue; } } - useBox.setValue(m); - slowCopyPropagationCount++; + if (isRedefined) { + continue; + } } + + useBox.setValue(m); + slowCopyPropagationCount++; } } } } } - - if (Options.v().verbose()) { - logger.debug("[" + b.getMethod().getName() + "] Propagated: " + fastCopyPropagationCount + " fast copies " - + slowCopyPropagationCount + " slow copies"); - } } + if (Options.v().verbose()) { + logger.debug("[" + b.getMethod().getName() + "] Propagated: " + fastCopyPropagationCount + " fast copies " + + slowCopyPropagationCount + " slow copies"); + } if (Options.v().time()) { Timers.v().propagatorTimer.end(); } diff --git a/src/main/java/soot/jimple/toolkits/scalar/DeadAssignmentEliminator.java b/src/main/java/soot/jimple/toolkits/scalar/DeadAssignmentEliminator.java index ac35885ab7b..4a0be779bbb 100644 --- a/src/main/java/soot/jimple/toolkits/scalar/DeadAssignmentEliminator.java +++ b/src/main/java/soot/jimple/toolkits/scalar/DeadAssignmentEliminator.java @@ -72,6 +72,7 @@ import soot.jimple.RemExpr; import soot.jimple.Stmt; import soot.options.Options; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.toolkits.scalar.LocalDefs; import soot.toolkits.scalar.LocalUses; import soot.toolkits.scalar.UnitValueBoxPair; @@ -237,7 +238,8 @@ protected void internalTransform(Body b, String phaseName, Map o // Add all the statements which are used to compute values // for the essential statements, recursively - final LocalDefs localDefs = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(b); + final LocalDefs localDefs = + G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(new FullExceptionalUnitGraph(b)); if (!allEssential) { Set essential = new HashSet(units.size()); diff --git a/src/main/java/soot/shimple/DefaultShimpleFactory.java b/src/main/java/soot/shimple/DefaultShimpleFactory.java index 347bf029dbb..d4251434bb9 100644 --- a/src/main/java/soot/shimple/DefaultShimpleFactory.java +++ b/src/main/java/soot/shimple/DefaultShimpleFactory.java @@ -35,6 +35,7 @@ import soot.toolkits.graph.DominatorsFinder; import soot.toolkits.graph.ExceptionalBlockGraph; import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.toolkits.graph.HashReversibleGraph; import soot.toolkits.graph.ReversibleGraph; import soot.toolkits.graph.SimpleDominatorsFinder; @@ -144,7 +145,7 @@ public UnitGraph getUnitGraph() { if (ug == null) { Body body = getBody(); UnreachableCodeEliminator.v().transform(body); - ug = new ExceptionalUnitGraph(body); + ug = new FullExceptionalUnitGraph(body); this.ug = ug; } return ug; diff --git a/src/main/java/soot/toolkits/graph/ExceptionalUnitGraph.java b/src/main/java/soot/toolkits/graph/ExceptionalUnitGraph.java index 32ebac07cf4..581a1f7a4b3 100644 --- a/src/main/java/soot/toolkits/graph/ExceptionalUnitGraph.java +++ b/src/main/java/soot/toolkits/graph/ExceptionalUnitGraph.java @@ -322,7 +322,7 @@ protected Map> buildExceptionDests(ThrowAnalysis * @return a Map which whose contents are equivalent to the input map, plus the information that * u throws caught to t. */ - private Map> addDestToMap(Map> map, Unit u, Trap t, + protected Map> addDestToMap(Map> map, Unit u, Trap t, ThrowableSet caught) { Collection dests = (map == null ? null : map.get(u)); if (dests == null) { diff --git a/src/main/java/soot/toolkits/graph/FullExceptionalUnitGraph.java b/src/main/java/soot/toolkits/graph/FullExceptionalUnitGraph.java new file mode 100644 index 00000000000..d83c7f295d3 --- /dev/null +++ b/src/main/java/soot/toolkits/graph/FullExceptionalUnitGraph.java @@ -0,0 +1,120 @@ +package soot.toolkits.graph; + +/*- + * #%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.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import soot.Body; +import soot.RefType; +import soot.Trap; +import soot.Unit; +import soot.toolkits.exceptions.PedanticThrowAnalysis; +import soot.toolkits.exceptions.ThrowAnalysis; +import soot.toolkits.exceptions.ThrowableSet; +import soot.util.Chain; + +/** + * Extension of {@link ExceptionalUnitGraph} that aligns more closely with the representation used by the JVM bytecode + * verifier. The standard {@link ExceptionalUnitGraph} will not add an exception edge for an exception table entry if an + * earlier entry already caught a broader exception type (i.e. the edge for the later entry will never actually execute + * during runtime). However, the JVM bytecode verifier considers all exceptional edges verbatim from the exception table and + * thus, may consider more possible paths in the CFG. Furthermore, this graph uses a {@link PedanticThrowAnalysis} to ensure + * that all Units covered by an exception table entry will have an edge to the exception handler which forces phi-node + * removal to back-propagate assignments all the way back to their original location to avoid "uninitialized register" errors + * from the JVM bytecode verifier. + * + * @author Timothy Hoffman + */ +public class FullExceptionalUnitGraph extends ExceptionalUnitGraph { + + public FullExceptionalUnitGraph(Body body) { + // Set 'omitExceptingUnitEdges' as false and use PedanticThrowAnalysis + // so that all units will have an edge into exception handler blocks. + this(body, PedanticThrowAnalysis.v(), false); + } + + /** + * IMPORTANT: This constructor should be used with care because the {@link ThrowAnalysis} should normally be + * {@link PedanticThrowAnalysis} for the most accurate result (this is what the recommended constructor + * {@link #FullExceptionalUnitGraph(soot.Body)} uses). + * + * @param body + * @param ta + */ + public FullExceptionalUnitGraph(Body body, ThrowAnalysis ta) { + this(body, ta, false); + } + + /** + * IMPORTANT: This constructor should be used with care because the {@link ThrowAnalysis} should normally be + * {@link PedanticThrowAnalysis} and 'omitExceptingUnitEdges' should normally be 'false' for the most accurate result (this + * is what the recommended constructor {@link #FullExceptionalUnitGraph(soot.Body)} uses). + * + * @param body + * @param ta + * @param omitExceptingUnitEdges + */ + public FullExceptionalUnitGraph(Body body, ThrowAnalysis ta, boolean omitExceptingUnitEdges) { + super(body); + initialize(ta, omitExceptingUnitEdges); + } + + @Override + protected Map> buildExceptionDests(ThrowAnalysis throwAnalysis) { + // Identical to the original except it doesn't track the uncaught + // throwables when multiple Traps cover the same Unit. That way, the full + // effect of all traps is reflected in the graph, even if some edges + // will never be used because an earlier trap subsumes a later one. + // + Map> result = null; + + final Chain traps = body.getTraps(); + if (!traps.isEmpty()) { + final ThrowableSet EMPTY = ThrowableSet.Manager.v().EMPTY; + final Chain units = body.getUnits(); + + // Record the caught exceptions. + for (Trap trap : traps) { + RefType catcher = trap.getException().getType(); + for (Iterator it = units.iterator(trap.getBeginUnit(), units.getPredOf(trap.getEndUnit())); it.hasNext();) { + Unit unit = it.next(); + ThrowableSet thrownSet = throwAnalysis.mightThrow(unit); + ThrowableSet.Pair catchableAs = thrownSet.whichCatchableAs(catcher); + if (!EMPTY.equals(catchableAs.getCaught())) { + result = addDestToMap(result, unit, trap, catchableAs.getCaught()); + } else { + assert (thrownSet.equals(catchableAs.getUncaught())) : + "ExceptionalUnitGraph.buildExceptionDests(): " + + "catchableAs.caught == EMPTY, but catchableAs.uncaught != thrownSet" + System.getProperty("line.separator") + + body.getMethod().getSubSignature() + " Unit: " + unit.toString() + System.getProperty("line.separator") + + " catchableAs.getUncaught() == " + catchableAs.getUncaught().toString() + + System.getProperty("line.separator") + " thrownSet == " + thrownSet.toString(); + } + } + } + } + return result == null ? Collections.emptyMap() : result; + } +} diff --git a/src/main/java/soot/toolkits/scalar/FastColorer.java b/src/main/java/soot/toolkits/scalar/FastColorer.java index cc7211a51ce..5f240b4fc90 100644 --- a/src/main/java/soot/toolkits/scalar/FastColorer.java +++ b/src/main/java/soot/toolkits/scalar/FastColorer.java @@ -38,9 +38,8 @@ import soot.Unit; import soot.Value; import soot.ValueBox; -import soot.options.Options; -import soot.toolkits.exceptions.PedanticThrowAnalysis; import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.util.ArraySet; /** @@ -58,17 +57,16 @@ private FastColorer() { public static void unsplitAssignColorsToLocals(Body unitBody, Map localToGroup, Map localToColor, Map groupToColorCount) { - // To understand why a pedantic throw analysis is required, see comment - // in assignColorsToLocals method - final ExceptionalUnitGraph unitGraph = - new ExceptionalUnitGraph(unitBody, PedanticThrowAnalysis.v(), Options.v().omit_excepting_unit_edges()); + // Build a FullExceptionalUnitGraph to prevent JVM bytecode verifier errors + // like "java.lang.VerifyError: Incompatible argument to function". + final ExceptionalUnitGraph unitGraph = new FullExceptionalUnitGraph(unitBody); final UnitInterferenceGraph intGraph = new UnitInterferenceGraph(unitBody, localToGroup, new SimpleLiveLocals(unitGraph), unitGraph); - Map localToOriginalName = new HashMap(); + Map localToOriginalName = new HashMap<>(); // Map each local variable to its original name - for (Local local : intGraph.getLocals()) { + for (Local local : unitBody.getLocals()) { String name = local.getName(); int signIndex = name.indexOf('#'); if (signIndex >= 0) { @@ -78,12 +76,12 @@ public static void unsplitAssignColorsToLocals(Body unitBody, Map } // maps an original name to the colors being used for it - Map> originalNameAndGroupToColors = new HashMap>(); + Map> originalNameAndGroupToColors = new HashMap<>(); // Assign a color for each local. { int[] freeColors = new int[10]; - for (Local local : intGraph.getLocals()) { + for (Local local : unitBody.getLocals()) { if (localToColor.containsKey(local)) { // Already assigned, probably a parameter continue; @@ -117,7 +115,7 @@ public static void unsplitAssignColorsToLocals(Body unitBody, Map StringGroupPair key = new StringGroupPair(localToOriginalName.get(local), group); List originalNameColors = originalNameAndGroupToColors.get(key); if (originalNameColors == null) { - originalNameColors = new ArrayList(); + originalNameColors = new ArrayList<>(); originalNameAndGroupToColors.put(key, originalNameColors); } @@ -150,17 +148,16 @@ public static void unsplitAssignColorsToLocals(Body unitBody, Map public static void assignColorsToLocals(Body unitBody, Map localToGroup, Map localToColor, Map groupToColorCount) { - // Build a CFG using a pedantic throw analysis to prevent JVM - // "java.lang.VerifyError: Incompatible argument to function" errors. - final ExceptionalUnitGraph unitGraph = - new ExceptionalUnitGraph(unitBody, PedanticThrowAnalysis.v(), Options.v().omit_excepting_unit_edges()); + // Build a FullExceptionalUnitGraph to prevent JVM bytecode verifier errors + // like "java.lang.VerifyError: Incompatible argument to function". + final ExceptionalUnitGraph unitGraph = new FullExceptionalUnitGraph(unitBody); final UnitInterferenceGraph intGraph = new UnitInterferenceGraph(unitBody, localToGroup, new SimpleLiveLocals(unitGraph), unitGraph); // Sort the locals first to maximize the locals per color. We first // assign those locals that have many conflicts and then assign the // easier ones to those color groups. - List sortedLocals = new ArrayList(intGraph.getLocals()); + List sortedLocals = new ArrayList<>(unitBody.getLocals()); Collections.sort(sortedLocals, new Comparator() { @Override public int compare(Local o1, Local o2) { @@ -211,18 +208,18 @@ public int compare(Local o1, Local o2) { } } - /** Implementation of a unit interference graph. */ + /** + * Implementation of a unit interference graph. + */ private static class UnitInterferenceGraph { // Maps a local to its interfering locals. final Map> localToLocals; - final List locals; public UnitInterferenceGraph(Body body, Map localToGroup, LiveLocals liveLocals, ExceptionalUnitGraph unitGraph) { - this.locals = new ArrayList(body.getLocals()); - this.localToLocals = new HashMap>(body.getLocalCount() * 2 + 1, 0.7f); + this.localToLocals = new HashMap<>(body.getLocalCount() * 2 + 1, 0.7f); // Go through code, noting interferences for (Unit unit : body.getUnits()) { @@ -262,7 +259,7 @@ public UnitInterferenceGraph(Body body, Map localToGrou if (defValue instanceof Local) { Local defLocal = (Local) defValue; - Set liveLocalsAtUnit = new HashSet(); + Set liveLocalsAtUnit = new HashSet<>(); for (Unit succ : unitGraph.getSuccsOf(unit)) { liveLocalsAtUnit.addAll(liveLocals.getLiveLocalsBefore(succ)); } @@ -277,16 +274,12 @@ public UnitInterferenceGraph(Body body, Map localToGrou } } - public List getLocals() { - return locals; - } - - public void setInterference(Local l1, Local l2) { + private void setInterference(Local l1, Local l2) { // We need the mapping in both directions // l1 -> l2 Set locals = localToLocals.get(l1); if (locals == null) { - locals = new ArraySet(); + locals = new ArraySet<>(); localToLocals.put(l1, locals); } locals.add(l2); @@ -294,7 +287,7 @@ public void setInterference(Local l1, Local l2) { // l2 -> l1 locals = localToLocals.get(l2); if (locals == null) { - locals = new ArraySet(); + locals = new ArraySet<>(); localToLocals.put(l2, locals); } locals.add(l1); @@ -315,7 +308,9 @@ public Local[] getInterferencesOf(Local l) { } } - /** Binds together a String and a Group. */ + /** + * Binds together a String and a Group. + */ private static class StringGroupPair { private final String string; private final Object group; diff --git a/src/main/java/soot/validation/CheckInitValidator.java b/src/main/java/soot/validation/CheckInitValidator.java index f28c85d128e..3564ae00ff3 100644 --- a/src/main/java/soot/validation/CheckInitValidator.java +++ b/src/main/java/soot/validation/CheckInitValidator.java @@ -31,6 +31,7 @@ import soot.ValueBox; import soot.toolkits.exceptions.ThrowAnalysisFactory; import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.toolkits.scalar.FlowSet; import soot.toolkits.scalar.InitAnalysis; @@ -43,7 +44,7 @@ public static CheckInitValidator v() { @Override public void validate(Body body, List exception) { - ExceptionalUnitGraph g = new ExceptionalUnitGraph(body, ThrowAnalysisFactory.checkInitThrowAnalysis(), false); + ExceptionalUnitGraph g = new FullExceptionalUnitGraph(body, ThrowAnalysisFactory.checkInitThrowAnalysis()); InitAnalysis analysis = new InitAnalysis(g); for (Unit s : body.getUnits()) { @@ -53,9 +54,8 @@ public void validate(Body body, List exception) { if (v instanceof Local) { Local l = (Local) v; if (!init.contains(l)) { - exception.add( - new ValidationException(s, "Local variable " + l.getName() + " is not definitively defined at this point", - "Warning: Local variable " + l + " not definitely defined at " + s + " in " + body.getMethod())); + String pfx = "Local variable " + l.getName() + " is not definitively defined at "; + exception.add(new ValidationException(s, pfx + "this point", "Warning: " + pfx + s + " in " + body.getMethod())); } } } diff --git a/src/main/java/soot/validation/UsesValidator.java b/src/main/java/soot/validation/UsesValidator.java index 14d272269a6..f7357b34693 100644 --- a/src/main/java/soot/validation/UsesValidator.java +++ b/src/main/java/soot/validation/UsesValidator.java @@ -22,8 +22,9 @@ * #L% */ -import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import soot.Body; import soot.G; @@ -31,9 +32,7 @@ import soot.Unit; import soot.Value; import soot.ValueBox; -import soot.toolkits.exceptions.PedanticThrowAnalysis; -import soot.toolkits.exceptions.ThrowAnalysis; -import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.FullExceptionalUnitGraph; import soot.toolkits.graph.UnitGraph; import soot.toolkits.scalar.LocalDefs; @@ -74,11 +73,10 @@ public void validate(Body body, List exception) { // Note that unreachable traps can be removed by setting jb.uce's // "remove-unreachable-traps" option to true. - ThrowAnalysis throwAnalysis = PedanticThrowAnalysis.v(); - UnitGraph g = new ExceptionalUnitGraph(body, throwAnalysis, false); - LocalDefs ld = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(g, true); + final UnitGraph g = new FullExceptionalUnitGraph(body); + final LocalDefs ld = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(g, true); + final Set locals = new HashSet<>(body.getLocals()); - Collection locals = body.getLocals(); for (Unit u : body.getUnits()) { for (ValueBox box : u.getUseBoxes()) { Value v = box.getValue(); diff --git a/src/systemTest/java/soot/shimple/Shimple1Test.java b/src/systemTest/java/soot/shimple/Shimple1Test.java new file mode 100644 index 00000000000..41ea7ec1394 --- /dev/null +++ b/src/systemTest/java/soot/shimple/Shimple1Test.java @@ -0,0 +1,492 @@ +package soot.shimple; + +/*- + * #%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.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.CheckClassAdapter; +import org.powermock.core.classloader.annotations.PowerMockIgnore; + +import soot.Body; +import soot.G; +import soot.ModulePathSourceLocator; +import soot.ModuleScene; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.jimple.JimpleBody; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; +import soot.validation.CheckInitValidator; +import soot.validation.UsesValidator; +import soot.validation.ValidationException; + +/** + * @author Timothy Hoffman + */ +@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*" }) +public class Shimple1Test extends AbstractTestingFramework { + + private static final boolean DEBUG_PRINT = false; + + private static final String TEST_CLASS = "soot.shimple.ShimpleTestInput"; + + @Override + protected void setupSoot() { + final Options opts = Options.v(); + + // Ensure Shimple form is generated to trigger the bug + opts.set_via_shimple(true); + + if (DEBUG_PRINT) { + opts.setPhaseOption("jb", "use-original-names:true"); + } + } + + @Test + public void testComplexPhi_1A() throws Exception { + final String TEST_METHOD = "sync"; + + SootMethod m = prepareTarget(methodSigFromComponents(TEST_CLASS, "boolean", TEST_METHOD), TEST_CLASS); + + Body b = m.retrieveActiveBody(); + if (DEBUG_PRINT) { + System.out.println("[testComplexPhi_1A] " + b.getClass().getName() + " = " + b); + } + Assert.assertTrue(b instanceof JimpleBody); + + // Ensure that the Soot validators pass + { + List exceptions = new ArrayList<>(); + CheckInitValidator.v().validate(b, exceptions); + UsesValidator.v().validate(b, exceptions); + if (DEBUG_PRINT) { + for (ValidationException e : exceptions) { + System.out.println(e); + } + } + Assert.assertTrue(exceptions.isEmpty()); + } + + // Convert the body to a runnable classfile, run, and test output. + Class c = generateClass(m.getDeclaringClass()); + + // If the generated bytecode does not pass JVM bytecode verifier, then + // an exception will occur when trying to create a new instance. + Object cInstance = c.newInstance(); + + Object retVal = c.getMethod(TEST_METHOD).invoke(cInstance); + Assert.assertEquals(Boolean.FALSE, retVal); + } + + @Test + public void testComplexPhi_1B() throws Exception { + final String TEST_METHOD = "hasMethod"; + + SootMethod m = prepareTarget(methodSigFromComponents(TEST_CLASS, "java.lang.Class", TEST_METHOD), TEST_CLASS); + + Body b = m.retrieveActiveBody(); + if (DEBUG_PRINT) { + System.out.println("[testComplexPhi_1B] " + b.getClass().getName() + " = " + b); + } + Assert.assertTrue(b instanceof JimpleBody); + + // Ensure that the Soot validators pass + { + List exceptions = new ArrayList<>(); + CheckInitValidator.v().validate(b, exceptions); + UsesValidator.v().validate(b, exceptions); + if (DEBUG_PRINT) { + for (ValidationException e : exceptions) { + System.out.println(e); + } + } + Assert.assertTrue(exceptions.isEmpty()); + } + + // Convert the body to a runnable classfile, run, and test output. + Class c = generateClass(m.getDeclaringClass()); + + // If the generated bytecode does not pass JVM bytecode verifier, then + // an exception will occur when trying to create a new instance. + Object cInstance = c.newInstance(); + + Object retVal = c.getMethod(TEST_METHOD).invoke(cInstance); + Assert.assertEquals(c, retVal); + } + + @Test + public void testComplexPhi_1C() throws Exception { + final String TEST_METHOD = "readProp"; + + SootMethod m = prepareTarget(methodSigFromComponents(TEST_CLASS, "java.io.InputStream", TEST_METHOD), TEST_CLASS); + + Body b = m.retrieveActiveBody(); + if (DEBUG_PRINT) { + System.out.println("[testComplexPhi_1C] " + b.getClass().getName() + " = " + b); + } + Assert.assertTrue(b instanceof JimpleBody); + + // Ensure that the Soot validators pass + { + List exceptions = new ArrayList<>(); + CheckInitValidator.v().validate(b, exceptions); + UsesValidator.v().validate(b, exceptions); + if (DEBUG_PRINT) { + for (ValidationException e : exceptions) { + System.out.println(e); + } + } + Assert.assertTrue(exceptions.isEmpty()); + } + + // Convert the body to a runnable classfile, run, and test output. + Class c = generateClass(m.getDeclaringClass()); + + // If the generated bytecode does not pass JVM bytecode verifier, then + // an exception will occur when trying to create a new instance. + Object cInstance = c.newInstance(); + + Object retVal = c.getMethod(TEST_METHOD).invoke(cInstance); + Assert.assertNull(retVal); + } + + /** + * TODO: on Java9 and Java11, only the java.* classes are being loaded. All of the others are not located in the + * "java.base" module but are in different standard modules. I'm not sure how to get Soot to load other modules. + * + * @throws Exception + */ + @Test + public void testStdLib() throws Exception { + final String rtJar = rtJar(); + final boolean isModuleJar = isModuleJar(rtJar); + + // Run both ASM and JVM verifier on these classes. + final List cls = new ArrayList<>(148); + // Run only the ASM verifier on these classes because they cannot + // be loaded successfully via the generateClass(..) method. + final List clsASM = new ArrayList<>(37); + if (!isModuleJar) { + // These are not present in JDK 11 + // + cls.add("com.sun.corba.se.impl.corba.ServerRequestImpl"); + cls.add("com.sun.corba.se.impl.corba.TCUtility"); + cls.add("com.sun.corba.se.impl.dynamicany.DynEnumImpl"); + cls.add("com.sun.corba.se.impl.dynamicany.DynFixedImpl"); + cls.add("com.sun.corba.se.impl.io.ObjectStreamClass"); + cls.add("com.sun.corba.se.impl.naming.cosnaming.TransientNameServer"); + cls.add("com.sun.corba.se.impl.naming.pcosnaming.ServantManagerImpl"); + cls.add("com.sun.corba.se.impl.oa.poa.POAPolicyMediatorImpl_R_USM"); + cls.add("com.sun.corba.se.impl.orbutil.ObjectStreamClassUtil_1_3"); + cls.add("com.sun.corba.se.impl.orbutil.ObjectStreamClassUtil_1_3$3"); + cls.add("com.sun.corba.se.impl.orbutil.threadpool.ThreadPoolManagerImpl$1"); + cls.add("com.sun.corba.se.impl.presentation.rmi.ReflectiveTie"); + cls.add("com.sun.corba.se.impl.presentation.rmi.StubInvocationHandlerImpl"); + cls.add("com.sun.corba.se.impl.resolver.BootstrapResolverImpl"); + cls.add("com.sun.corba.se.impl.util.Utility"); + cls.add("com.sun.istack.internal.localization.Localizer"); + cls.add("com.sun.jndi.cosnaming.CNCtx"); + cls.add("com.sun.org.apache.bcel.internal.util.SecuritySupport$3"); + cls.add("com.sun.org.apache.xalan.internal.utils.SecuritySupport"); + cls.add("com.sun.org.apache.xalan.internal.utils.SecuritySupport$3"); + cls.add("com.sun.org.apache.xalan.internal.xslt.Process"); + cls.add("com.sun.org.apache.xalan.internal.xsltc.cmdline.Transform"); + cls.add("com.sun.org.apache.xerces.internal.util.XMLCatalogResolver"); + cls.add("com.sun.org.apache.xerces.internal.utils.SecuritySupport"); + cls.add("com.sun.org.apache.xerces.internal.utils.SecuritySupport$3"); + cls.add("com.sun.org.apache.xerces.internal.xinclude.SecuritySupport$3"); + cls.add("com.sun.org.apache.xml.internal.resolver.CatalogManager"); + cls.add("com.sun.org.apache.xml.internal.resolver.tools.CatalogResolver"); + cls.add("com.sun.org.apache.xml.internal.serialize.SecuritySupport$3"); + cls.add("com.sun.org.apache.xml.internal.serialize.BaseMarkupSerializer"); + cls.add("com.sun.org.apache.xml.internal.serialize.HTMLdtd"); + cls.add("com.sun.org.apache.xml.internal.serializer.CharInfo"); + cls.add("com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory"); + cls.add("com.sun.org.apache.xml.internal.utils.XMLReaderManager"); + cls.add("com.sun.xml.internal.bind.v2.runtime.MarshallerImpl"); + cls.add("com.sun.xml.internal.fastinfoset.stax.factory.StAXOutputFactory"); + cls.add("com.sun.xml.internal.messaging.saaj.client.p2p.HttpSOAPConnection"); + cls.add("com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeBodyPart"); + cls.add("com.sun.xml.internal.org.jvnet.mimepull.MIMEPart"); + cls.add("com.sun.xml.internal.ws.client.dispatch.DispatchImpl"); + cls.add("com.sun.xml.internal.ws.commons.xmlutil.Converter"); + cls.add("com.sun.xml.internal.ws.encoding.ContentTypeImpl"); + cls.add("com.sun.xml.internal.ws.fault.SOAPFaultBuilder"); + cls.add("com.sun.xml.internal.ws.handler.HandlerProcessor"); + cls.add("com.sun.xml.internal.ws.policy.jaxws.PolicyWSDLParserExtension"); + cls.add("com.sun.xml.internal.ws.transport.http.HttpAdapter$Oneway"); + cls.add("javax.activation.SecuritySupport$3"); + cls.add("javax.activation.SecuritySupport$4"); + cls.add("javax.swing.JOptionPane$ModalPrivilegedAction"); + cls.add("javax.xml.ws.spi.FactoryFinder"); + cls.add("sun.applet.AppletPanel$6"); + cls.add("sun.misc.Launcher"); + cls.add("sun.misc.Service$LazyIterator"); + cls.add("sun.misc.URLClassPath"); + cls.add("sun.rmi.transport.proxy.HttpSendSocket"); + cls.add("sun.tracing.ProviderSkeleton"); + // + clsASM.add("com.sun.corba.se.impl.dynamicany.DynAnyComplexImpl"); + clsASM.add("com.sun.corba.se.impl.dynamicany.DynUnionImpl"); + clsASM.add("com.sun.corba.se.impl.encoding.CDRInputStream_1_0"); + clsASM.add("com.sun.corba.se.impl.transport.SelectorImpl"); + clsASM.add("com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty"); + clsASM.add("java.awt.KeyboardFocusManager$5"); + clsASM.add("java.util.ServiceLoader$LazyIterator"); + clsASM.add("javax.xml.bind.ContextFinder"); + clsASM.add("sun.management.Agent"); + } + cls.add("com.sun.imageio.plugins.png.PNGImageReader"); + cls.add("com.sun.jmx.remote.util.EnvHelp"); + cls.add("com.sun.jndi.dns.DnsContextFactory"); + cls.add("com.sun.jndi.ldap.Connection"); + cls.add("com.sun.jndi.ldap.LdapClient"); + cls.add("com.sun.jndi.ldap.LdapCtx"); + cls.add("com.sun.naming.internal.FactoryEnumeration"); + cls.add("com.sun.naming.internal.ResourceManager"); + cls.add("com.sun.org.apache.xalan.internal.lib.ExsltDynamic"); + cls.add("com.sun.org.apache.xalan.internal.xsltc.compiler.Parser"); + cls.add("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"); + cls.add("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl"); + cls.add("com.sun.org.apache.xerces.internal.impl.XMLScanner"); + cls.add("com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator"); + cls.add("com.sun.org.apache.xerces.internal.impl.xpath.regex.REUtil"); + cls.add("com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader"); + cls.add("com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator"); + cls.add("com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDAbstractTraverser"); + cls.add("com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler"); + cls.add("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); + cls.add("com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser"); + cls.add("com.sun.org.apache.xerces.internal.parsers.DOMParser"); + cls.add("com.sun.org.apache.xerces.internal.xinclude.XIncludeHandler"); + cls.add("com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces"); + cls.add("com.sun.org.apache.xml.internal.security.keys.storage.implementations.CertsInFilesystemDirectoryResolver"); + cls.add("com.sun.org.apache.xml.internal.security.utils.JavaUtils"); + cls.add("com.sun.org.apache.xml.internal.security.utils.XalanXPathAPI"); + cls.add("com.sun.org.apache.xpath.internal.XPath"); + cls.add("com.sun.rowset.internal.CachedRowSetReader"); + cls.add("com.sun.security.auth.module.KeyStoreLoginModule"); + cls.add("com.sun.security.auth.module.LdapLoginModule"); + cls.add("javax.imageio.ImageIO$ImageReaderIterator"); + cls.add("javax.imageio.ImageIO$ImageWriterIterator"); + cls.add("javax.management.loading.MLet"); + cls.add("javax.management.modelmbean.DescriptorSupport"); + cls.add("javax.management.monitor.Monitor"); + cls.add("javax.management.remote.rmi.RMIConnector"); + cls.add("javax.management.remote.rmi.RMIConnector$RMIClientCommunicatorAdmin"); + cls.add("javax.naming.spi.NamingManager"); + cls.add("javax.smartcardio.TerminalFactory"); + cls.add("javax.sql.rowset.spi.ProviderImpl"); + cls.add("javax.swing.BufferStrategyPaintManager$BufferInfo"); + cls.add("javax.swing.plaf.basic.BasicLookAndFeel"); + cls.add("javax.swing.text.CompositeView"); + cls.add("javax.swing.text.html.StyleSheet$ListPainter"); + cls.add("org.jcp.xml.dsig.internal.dom.DOMReference"); + cls.add("org.xml.sax.helpers.XMLReaderFactory"); + cls.add("sun.awt.SunToolkit"); + cls.add("sun.awt.im.InputContext"); + cls.add("sun.font.FontUtilities$1"); + cls.add("sun.font.SunFontManager"); + cls.add("sun.font.SunFontManager$2"); + cls.add("sun.font.TrueTypeFont"); + cls.add("sun.font.Type1Font"); + cls.add("sun.instrument.InstrumentationImpl"); + cls.add("sun.java2d.opengl.OGLRenderQueue$QueueFlusher"); + cls.add("sun.net.NetProperties"); + cls.add("sun.net.ResourceManager"); + cls.add("sun.net.ftp.impl.FtpClient"); + cls.add("sun.net.ftp.impl.FtpClient$MLSxParser"); + cls.add("sun.net.www.MimeTable"); + cls.add("sun.net.www.http.HttpClient"); + cls.add("sun.net.www.http.KeepAliveStreamCleaner"); + cls.add("sun.net.www.protocol.http.BasicAuthentication"); + cls.add("sun.net.www.protocol.http.HttpURLConnection"); + cls.add("sun.net.www.protocol.http.HttpURLConnection$ErrorStream"); + cls.add("sun.nio.ch.FileChannelImpl"); + cls.add("sun.nio.ch.SocketChannelImpl"); + cls.add("sun.rmi.registry.RegistryImpl"); + cls.add("sun.rmi.server.Activation"); + cls.add("sun.rmi.server.LoaderHandler"); + cls.add("sun.rmi.server.UnicastServerRef"); + cls.add("sun.rmi.transport.StreamRemoteCall"); + cls.add("sun.rmi.transport.Transport"); + cls.add("sun.security.jgss.GSSNameImpl"); + cls.add("sun.security.jgss.krb5.Krb5Context"); + cls.add("sun.security.jgss.krb5.Krb5NameElement"); + cls.add("sun.security.jgss.krb5.Krb5Util"); + cls.add("sun.security.jgss.spnego.SpNegoContext"); + cls.add("sun.security.jgss.wrapper.GSSNameElement"); + cls.add("sun.security.krb5.Credentials"); + cls.add("sun.security.krb5.KdcComm$KdcCommunication"); + cls.add("sun.security.krb5.KrbServiceLocator"); + cls.add("sun.security.krb5.internal.ktab.KeyTabOutputStream"); + cls.add("sun.security.krb5.internal.rcache.DflCache$Storage"); + cls.add("sun.security.pkcs.PKCS7"); + cls.add("sun.security.provider.PolicyFile"); + cls.add("sun.security.rsa.RSAPadding"); + cls.add("sun.security.tools.keytool.Main"); + cls.add("sun.security.util.KeyUtil"); + cls.add("sun.swing.SwingUtilities2"); + cls.add("sun.tools.jar.Main"); + // + clsASM.add("com.sun.org.apache.xalan.internal.xsltc.compiler.FunctionCall"); + clsASM.add("java.awt.Font"); + clsASM.add("java.awt.datatransfer.SystemFlavorMap"); + clsASM.add("java.awt.font.TextLayout"); + clsASM.add("java.beans.Beans"); + clsASM.add("java.beans.PropertyDescriptor"); + clsASM.add("java.io.FileDescriptor"); + clsASM.add("java.lang.CharacterName"); + clsASM.add("java.lang.ClassLoader"); + clsASM.add("java.net.DefaultDatagramSocketImplFactory"); + clsASM.add("java.net.InetAddress"); + clsASM.add("java.net.ServerSocket"); + clsASM.add("java.net.SocketPermission"); + clsASM.add("java.net.URL"); + clsASM.add("java.net.URLConnection"); + clsASM.add("java.net.URLDecoder"); + clsASM.add("java.net.URLEncoder"); + clsASM.add("java.security.Security"); + clsASM.add("java.time.Month"); + clsASM.add("java.time.MonthDay"); + clsASM.add("java.time.Year"); + clsASM.add("java.time.YearMonth"); + clsASM.add("java.util.Formatter"); + clsASM.add("java.util.concurrent.CyclicBarrier"); + clsASM.add("java.util.jar.Pack200"); + clsASM.add("java.util.logging.FileHandler"); + clsASM.add("java.util.prefs.AbstractPreferences"); + clsASM.add("javax.security.auth.kerberos.KerberosTicket"); + + final int NUM_CLASSES = cls.size() + clsASM.size(); + + // Run initial pass to ensure they are valid when processed without using Shimple form. + { + int pass1 = testStdLibRun(rtJar, cls, false, false); + int pass2 = testStdLibRun(rtJar, clsASM, false, true); + Assert.assertEquals("ORIGINAL classes (total " + NUM_CLASSES + ") with errors!", 0, NUM_CLASSES - pass1 - pass2); + } + + // Reset Soot and process the classes but use Shimple this time to trigger bug. + { + int pass1 = testStdLibRun(rtJar, cls, true, false); + int pass2 = testStdLibRun(rtJar, clsASM, true, true); + Assert.assertEquals("MODIFIED classes (total " + NUM_CLASSES + ") with errors!", 0, NUM_CLASSES - pass1 - pass2); + } + } + + private static String rtJar() throws IOException { + Path p = Paths.get(System.getProperty("java.home"), "lib", "rt.jar"); + return Files.exists(p) ? p.toRealPath().toString() : null; + } + + private static boolean isModuleJar(String rtJar) { + return (rtJar == null); + } + + private int testStdLibRun(String rtJar, List checkClasses, boolean useShimple, boolean onlyASM) { + final Scene scene = customSetupLib(rtJar, checkClasses, useShimple); + int success = 0; + for (String name : checkClasses) { + try { + SootClass sc = getOrResolveSootClass(scene, name, SootClass.BODIES); + Assert.assertNotNull(sc); + Assert.assertEquals(name, sc.getName()); + + { + // Run ASM verifier and ensure the message is empty + byte[] c = generateBytecode(sc); + StringWriter stringWriter = new StringWriter(); + CheckClassAdapter.verify(new ClassReader(c), false, new PrintWriter(stringWriter)); + String verifyMsg = stringWriter.toString(); + Assert.assertTrue(verifyMsg, verifyMsg.isEmpty()); + } + + if (!onlyASM) { + // If the generated bytecode does not pass JVM bytecode verifier, + // then an exception will occur when trying to obtain "declared" + // fields, methods, constructors, etc. + Class c = generateClass(sc); + Assert.assertNotNull(c.getDeclaredFields()); + Assert.assertNotNull(c.getDeclaredConstructors()); + Assert.assertNotNull(c.getDeclaredMethods()); + } + + success++; + } catch (Throwable t) { + if (DEBUG_PRINT) { + String kind = useShimple ? "ShimpleBody" : "JimpleBody"; + System.out.println("[testStdLib] ERROR in a " + kind + " in " + name + " " + t); + t.printStackTrace(System.out); + } + } + } + return success; + } + + private Scene customSetupLib(String rtJar, List classNames, boolean useShimple) { + G.reset(); + + final Options opts = Options.v(); + + opts.set_whole_program(true); + opts.set_output_format(Options.output_format_none); + opts.set_allow_phantom_refs(true); + opts.set_no_bodies_for_excluded(true); + opts.set_exclude(Collections.singletonList("java.*")); + opts.set_include(classNames); + opts.set_validate(true); + + final boolean isModuleJar = isModuleJar(rtJar); + if (isModuleJar) { + opts.set_soot_modulepath(ModulePathSourceLocator.DUMMY_CLASSPATH_JDK9_FS); + } else { + opts.set_soot_classpath(rtJar); + opts.set_process_dir(Collections.singletonList(rtJar)); + } + + opts.set_via_shimple(useShimple); + // opts.setPhaseOption("bb.lp", "unsplit-original-locals:true"); + + if (DEBUG_PRINT) { + opts.setPhaseOption("jb", "use-original-names:true"); + } + + // NOTE: must obtain Scene after all options are set + Scene scene = isModuleJar ? ModuleScene.v() : Scene.v(); + scene.loadNecessaryClasses(); + runSoot(); + return scene; + } +} diff --git a/src/systemTest/java/soot/shimple/Shimple2Test.java b/src/systemTest/java/soot/shimple/Shimple2Test.java new file mode 100644 index 00000000000..8db9799b98b --- /dev/null +++ b/src/systemTest/java/soot/shimple/Shimple2Test.java @@ -0,0 +1,101 @@ +package soot.shimple; + +/*- + * #%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.List; + +import org.junit.Assert; +import org.junit.Test; +import org.powermock.core.classloader.annotations.PowerMockIgnore; + +import soot.Body; +import soot.SootMethod; +import soot.jimple.JimpleBody; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; +import soot.validation.CheckInitValidator; +import soot.validation.UsesValidator; +import soot.validation.ValidationException; + +/** + * @author Timothy Hoffman + */ +@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*" }) +public class Shimple2Test extends AbstractTestingFramework { + + private static final boolean DEBUG_PRINT = false; + + private static final String TEST_CLASS = "soot.shimple.ShimpleTestInput"; + + @Override + protected void setupSoot() { + final Options opts = Options.v(); + + // Ensure Shimple form is generated to trigger the bug + opts.set_via_shimple(true); + + // Enable Jimple optimization pack to demonstrate that both the DeadAssignmentEliminator and CopyPropagator can still + // cause a java.lang.VerifyError when the exception table contains subsumed exceptions and -via-shimple was used. + opts.setPhaseOption("jop", "enabled:true"); + + if (DEBUG_PRINT) { + opts.setPhaseOption("jb", "use-original-names:true"); + } + } + + @Test + public void testComplexPhi_2A() throws Exception { + final String TEST_METHOD = "sync"; + + SootMethod m = prepareTarget(methodSigFromComponents(TEST_CLASS, "boolean", TEST_METHOD), TEST_CLASS); + + Body b = m.retrieveActiveBody(); + if (DEBUG_PRINT) { + System.out.println("[testComplexPhi_2A] " + b.getClass().getName() + " = " + b); + } + Assert.assertTrue(b instanceof JimpleBody); + + // Ensure that the Soot validators pass + { + List exceptions = new ArrayList<>(); + CheckInitValidator.v().validate(b, exceptions); + UsesValidator.v().validate(b, exceptions); + if (DEBUG_PRINT) { + for (ValidationException e : exceptions) { + System.out.println(e); + } + } + Assert.assertTrue(exceptions.isEmpty()); + } + + // Convert the body to a runnable classfile, run, and test output. + Class c = generateClass(m.getDeclaringClass()); + + // If the generated bytecode does not pass JVM bytecode verifier, then + // an exception will occur when trying to create a new instance. + Object cInstance = c.newInstance(); + + Object retVal = c.getMethod(TEST_METHOD).invoke(cInstance); + Assert.assertEquals(Boolean.FALSE, retVal); + } +} diff --git a/src/systemTest/targets/soot/shimple/ShimpleTestInput.java b/src/systemTest/targets/soot/shimple/ShimpleTestInput.java new file mode 100644 index 00000000000..ba0cdcdc4e8 --- /dev/null +++ b/src/systemTest/targets/soot/shimple/ShimpleTestInput.java @@ -0,0 +1,93 @@ +package soot.shimple; + +/*- + * #%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.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Timothy Hoffman + */ +public class ShimpleTestInput { + + public boolean sync() throws Exception { + boolean success = false; + IOException exc; + for (int retry = 0; !success && retry < 2; retry++) { + FileInputStream file = null; + try { + try { + file = new FileInputStream("delete_me.txt"); + success = true; + } finally { + if (file != null) { + file.close(); + } + } + } catch (IOException ioe) { + exc = ioe; + Thread.sleep(5); + } + } + return success; + } + + public Class hasMethod() { + Class clazz = null; + try { + if (clazz == null) { + clazz = ShimpleTestInput.class; + } + clazz.getDeclaredMethod("toString"); + } catch (NoSuchMethodException ex) { + } + return clazz; + } + + public InputStream readProp() { + InputStream is = null; + try { + synchronized (this) { + File f = new File("in.properties"); + is = new FileInputStream(f); + } + } catch (FileNotFoundException ex) { + } + return is; + } + + /** + * Run this test from the command line as "java -cp soot\target\systemTest-target-classes soot.shimple.ShimpleTestInput" + * and observe that it runs without error and prints "false" and then "true" to the command line. + * + * @param args + * + * @throws Exception + */ + public static void main(String[] args) throws Exception { + System.out.println(new ShimpleTestInput().sync()); + System.out.println(new ShimpleTestInput().hasMethod() == ShimpleTestInput.class); + } +}