diff --git a/sootup.analysis.interprocedural/src/test/java/sootup/analysis/interprocedural/ifds/IFDSTaintAnalysisProblem.java b/sootup.analysis.interprocedural/src/test/java/sootup/analysis/interprocedural/ifds/IFDSTaintAnalysisProblem.java index 1f351bca8a3..ca3be4e1be7 100644 --- a/sootup.analysis.interprocedural/src/test/java/sootup/analysis/interprocedural/ifds/IFDSTaintAnalysisProblem.java +++ b/sootup.analysis.interprocedural/src/test/java/sootup/analysis/interprocedural/ifds/IFDSTaintAnalysisProblem.java @@ -30,8 +30,8 @@ import heros.flowfunc.Identity; import heros.flowfunc.KillAll; import java.util.*; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Immediate; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Value; import sootup.core.jimple.common.constant.StringConstant; import sootup.core.jimple.common.expr.AbstractInvokeExpr; @@ -93,7 +93,7 @@ public FlowFunction getCallToReturnFlowFunction(Stmt callSite, Stmt retur @Override protected Value createZeroValue() { - return new Local("<>", NullType.getInstance()); + return Jimple.newLocal("<>", NullType.getInstance()); } FlowFunction getNormalFlow(Stmt curr, Stmt succ) { diff --git a/sootup.callgraph/src/test/java/sootup/callgraph/InstantiateClassValueVisitorTest.java b/sootup.callgraph/src/test/java/sootup/callgraph/InstantiateClassValueVisitorTest.java index 109bf136483..8c14e4444e1 100644 --- a/sootup.callgraph/src/test/java/sootup/callgraph/InstantiateClassValueVisitorTest.java +++ b/sootup.callgraph/src/test/java/sootup/callgraph/InstantiateClassValueVisitorTest.java @@ -8,8 +8,8 @@ import java.util.List; import org.junit.jupiter.api.Test; import sootup.core.IdentifierFactory; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Immediate; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Value; import sootup.core.jimple.common.constant.BooleanConstant; import sootup.core.jimple.common.constant.DoubleConstant; @@ -168,17 +168,17 @@ private void fillList(List listWithAllValues, View view) { listWithAllValues.add(new JXorExpr(stringConstant, stringConstant)); listWithAllValues.add( new JSpecialInvokeExpr( - new Local("a", StringClass), + Jimple.newLocal("a", StringClass), toStringMethod, Collections.singletonList(stringConstant))); listWithAllValues.add( new JVirtualInvokeExpr( - new Local("a", StringClass), + Jimple.newLocal("a", StringClass), toStringMethod, Collections.singletonList(stringConstant))); listWithAllValues.add( new JInterfaceInvokeExpr( - new Local("a", StringClass), + Jimple.newLocal("a", StringClass), toStringMethod, Collections.singletonList(stringConstant))); listWithAllValues.add( @@ -196,13 +196,14 @@ private void fillList(List listWithAllValues, View view) { listWithAllValues.add(new JLengthExpr(stringConstant)); listWithAllValues.add(new JNegExpr(stringConstant)); listWithAllValues.add(new JStaticFieldRef(stringField)); - listWithAllValues.add(new JInstanceFieldRef(new Local("a", StringClass), stringField)); - listWithAllValues.add(new JArrayRef(new Local("a", StringClass), stringConstant)); + listWithAllValues.add(new JInstanceFieldRef(Jimple.newLocal("a", StringClass), stringField)); + listWithAllValues.add(new JArrayRef(Jimple.newLocal("a", StringClass), stringConstant)); listWithAllValues.add(new JParameterRef(StringClass, 3)); listWithAllValues.add(new JCaughtExceptionRef(StringClass)); listWithAllValues.add(new JThisRef(StringClass)); - listWithAllValues.add(new Local("a", StringClass)); + listWithAllValues.add(Jimple.newLocal("a", StringClass)); listWithAllValues.add( - new JPhiExpr(Collections.singletonList(new Local("a", StringClass)), new HashMap<>())); + new JPhiExpr( + Collections.singletonList(Jimple.newLocal("a", StringClass)), new HashMap<>())); } } diff --git a/sootup.core/src/main/java/sootup/core/jimple/Jimple.java b/sootup.core/src/main/java/sootup/core/jimple/Jimple.java index 0e5170f2553..fa6b83263ea 100644 --- a/sootup.core/src/main/java/sootup/core/jimple/Jimple.java +++ b/sootup.core/src/main/java/sootup/core/jimple/Jimple.java @@ -23,6 +23,7 @@ */ import java.util.*; +import org.jspecify.annotations.NonNull; import sootup.core.IdentifierFactory; import sootup.core.graph.BasicBlock; import sootup.core.jimple.basic.*; @@ -477,7 +478,27 @@ public static JSwitchStmt newLookupSwitchStmt( /** Constructs a Local with the given name and type. */ public static Local newLocal(String name, Type t) { - return new Local(name, t); + return new LocalImpl(name, t); + } + + /** + * Constructs a local from a bytecode slot index, or a generic local for {@code -1}. + * + * @deprecated Use {@link #newSlotLocal(String, Type, int)} or {@link #newLocal(String, Type)}. + */ + @Deprecated + public static Local newLocal(String name, Type t, int slotIndex) { + return slotIndex == -1 ? newLocal(name, t) : newSlotLocal(name, t, slotIndex); + } + + /** Constructs a temporary originating from the JVM operand stack. */ + public static StackLocal newStackLocal(String name, Type t) { + return new StackLocalImpl(name, t); + } + + /** Constructs a local originating from the given nonnegative JVM local variable slot. */ + public static SlotLocal newSlotLocal(String name, Type t, int slotIndex) { + return new SlotLocalImpl(name, t, slotIndex); } /** Constructs a JStaticFieldRef(FieldSignature) grammar chunk. */ @@ -517,4 +538,71 @@ public static JNewMultiArrayExpr newNewMultiArrayExpr(ArrayType type, List. + * #L% + */ + +import java.util.*; +import org.jspecify.annotations.NonNull; +import sootup.core.graph.ControlFlowGraph; +import sootup.core.jimple.Jimple; +import sootup.core.jimple.basic.JimpleComparator; +import sootup.core.jimple.common.stmt.AbstractDefinitionStmt; +import sootup.core.jimple.common.stmt.Stmt; +import sootup.core.jimple.visitor.ImmediateVisitor; +import sootup.core.types.Type; +import sootup.core.types.VoidType; +import sootup.core.util.printer.StmtPrinter; + +/** + * Shared immutable implementation of {@link Local}. + * + *

Prefer to use the factory methods in {@link Jimple}. + * + * @author Linghui Luo + */ +public abstract class AbstractLocal implements Local { + + @NonNull private final String name; + @NonNull private final Type type; + + /** Constructs a local with the given name and type. */ + protected AbstractLocal(@NonNull String name, @NonNull Type type) { + this.name = name; + if (type instanceof VoidType) { + throw new RuntimeException("Type should not be VoidType"); + } else { + this.type = type; + } + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof Local)) { + return false; + } + return name.equals(((Local) o).getName()); + } + + @Override + public final int hashCode() { + return Objects.hashCode(name); + } + + @Override + public boolean equivTo(@NonNull Object o, @NonNull JimpleComparator comparator) { + return comparator.caseLocal(this, o); + } + + @Override + public int equivHashCode() { + return Objects.hash(name, type); + } + + /** Returns the name of this object. */ + @NonNull + public String getName() { + return name; + } + + /** Returns the type of this local. */ + @NonNull + @Override + public Type getType() { + return type; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public void toString(@NonNull StmtPrinter up) { + up.local(this); + } + + @Override + public void collectUses(List collector) {} + + /** returns a List that can contain: Locals, JFieldRefs, JArrayRefs */ + public List getDefs(Collection defs) { + List localDefs = new ArrayList<>(); + for (Stmt stmt : defs) { + if (stmt instanceof AbstractDefinitionStmt + && ((AbstractDefinitionStmt) stmt).getLeftOp().equals(this)) { + localDefs.add((AbstractDefinitionStmt) stmt); + } + } + return localDefs; + } + + /** + * Get all definition-stmts which define the given local used by the given stmt. + * + * @param graph a stmt graph which contains the given stmts. + * @param stmt a stmt which uses the given local. + */ + public List getDefsForLocalUse(ControlFlowGraph graph, Stmt stmt) { + if (stmt.getUses().stream().noneMatch(v -> v == this)) { + throw new RuntimeException(stmt + " doesn't use the local " + this); + } + List defStmts = new ArrayList<>(); + Set visited = new HashSet<>(); + + Deque queue = new ArrayDeque<>(); + // Seed the search queue with predecessors of 'stmt' rather than 'stmt' itself. + // When a statement both uses and defines the same local (e.g. `x = x + 1`), starting at `stmt` + // immediately matched `stmt` as a definition, short-circuiting and erroneously reporting that + // `x` is defined by `stmt` itself before it is evaluated. Seeding with predecessors searches + // the reaching definitions flowing into `stmt`. + if (graph.containsNode(stmt)) { + queue.addAll(graph.predecessors(stmt)); + } + while (!queue.isEmpty()) { + Stmt s = queue.removeFirst(); + if (!visited.contains(s)) { + visited.add(s); + if (s instanceof AbstractDefinitionStmt && s.getDef().get().equivTo(this)) { + defStmts.add(s); + } else { + if (graph.containsNode(s)) { + queue.addAll(graph.predecessors(s)); + } + } + } + } + return defStmts; + } + + public List getStmtsUsingOrDefiningthisLocal(Collection stmts, Stmt removedStmt) { + List localOccurrences = new ArrayList<>(); + for (Stmt stmt : stmts) { + if (stmt.equivTo(removedStmt)) continue; + List stmtUsesAndDefs = stmt.getUsesAndDefs(); + for (Value stmtUse : stmtUsesAndDefs) { + if (stmtUse instanceof Local && stmtUse.equivTo(this)) { + localOccurrences.add(stmt); + } + } + } + return localOccurrences; + } + + @Override + public V accept(@NonNull V v) { + v.caseLocal(this); + return v; + } +} diff --git a/sootup.core/src/main/java/sootup/core/jimple/common/Local.java b/sootup.core/src/main/java/sootup/core/jimple/common/Local.java index 92617cdf723..be851cfadec 100644 --- a/sootup.core/src/main/java/sootup/core/jimple/common/Local.java +++ b/sootup.core/src/main/java/sootup/core/jimple/common/Local.java @@ -22,11 +22,11 @@ * #L% */ -import java.util.*; +import java.util.Collection; +import java.util.List; import org.jspecify.annotations.NonNull; import sootup.core.graph.ControlFlowGraph; import sootup.core.jimple.Jimple; -import sootup.core.jimple.basic.JimpleComparator; import sootup.core.jimple.basic.LocalGenerator; import sootup.core.jimple.common.stmt.AbstractDefinitionStmt; import sootup.core.jimple.common.stmt.Stmt; @@ -34,157 +34,27 @@ import sootup.core.jimple.visitor.ImmediateVisitor; import sootup.core.model.Body; import sootup.core.types.Type; -import sootup.core.types.VoidType; -import sootup.core.util.printer.StmtPrinter; /** - * Local variable in {@link Body}. Use {@link LocalGenerator} to generate locals. + * Local variable in a {@link Body}. Use {@link LocalGenerator} or the factories in {@link Jimple} + * to create locals. JVM slot provenance is available through {@link SlotLocal}. * - *

Prefer to use the factory methods in {@link Jimple}. - * - * @author Linghui Luo + *

Implementations must compare locals by name in {@link Object#equals(Object)} and {@link + * Object#hashCode()}, and by name and type for Jimple equivalence. Extend {@link AbstractLocal} to + * inherit this contract. */ -public class Local implements Immediate, LValue, Acceptor { - - @NonNull private final String name; - @NonNull private final Type type; - - /** Constructs a JimpleLocal of the given name and type. */ - public Local(@NonNull String name, @NonNull Type type) { - this.name = name; - if (type instanceof VoidType) { - throw new RuntimeException("Type should not be VoidType"); - } else { - this.type = type; - } - } - - @Override - public final boolean equals(Object o) { - if (!(o instanceof Local)) { - return false; - } - return name.equals(((Local) o).getName()); - } - - @Override - public final int hashCode() { - return Objects.hashCode(name); - } - - @Override - public boolean equivTo(@NonNull Object o, @NonNull JimpleComparator comparator) { - return comparator.caseLocal(this, o); - } - - @Override - public int equivHashCode() { - return Objects.hash(name, type); - } - - /** Returns the name of this object. */ - @NonNull - public String getName() { - return name; - } - - /** Returns the type of this local. */ - @NonNull - @Override - public Type getType() { - return type; - } - - @Override - public String toString() { - return getName(); - } - - @Override - public void toString(@NonNull StmtPrinter up) { - up.local(this); - } - - @Override - public void collectUses(List collector) {} - - /** returns a List that can contain: Locals, JFieldRefs, JArrayRefs */ - public List getDefs(Collection defs) { - List localDefs = new ArrayList<>(); - for (Stmt stmt : defs) { - if (stmt instanceof AbstractDefinitionStmt - && ((AbstractDefinitionStmt) stmt).getLeftOp().equals(this)) { - localDefs.add((AbstractDefinitionStmt) stmt); - } - } - return localDefs; - } - - /** - * Get all definition-stmts which define the given local used by the given stmt. - * - * @param graph a stmt graph which contains the given stmts. - * @param stmt a stmt which uses the given local. - */ - public List getDefsForLocalUse(ControlFlowGraph graph, Stmt stmt) { - if (stmt.getUses().stream().noneMatch(v -> v == this)) { - throw new RuntimeException(stmt + " doesn't use the local " + this); - } - List defStmts = new ArrayList<>(); - Set visited = new HashSet<>(); +public interface Local extends Immediate, LValue, Acceptor { + @NonNull String getName(); - Deque queue = new ArrayDeque<>(); - // Seed the search queue with predecessors of 'stmt' rather than 'stmt' itself. - // When a statement both uses and defines the same local (e.g. `x = x + 1`), starting at `stmt` - // immediately matched `stmt` as a definition, short-circuiting and erroneously reporting that - // `x` is defined by `stmt` itself before it is evaluated. Seeding with predecessors searches - // the reaching definitions flowing into `stmt`. - if (graph.containsNode(stmt)) { - queue.addAll(graph.predecessors(stmt)); - } - while (!queue.isEmpty()) { - Stmt s = queue.removeFirst(); - if (!visited.contains(s)) { - visited.add(s); - if (s instanceof AbstractDefinitionStmt && s.getDef().get().equivTo(this)) { - defStmts.add(s); - } else { - if (graph.containsNode(s)) { - queue.addAll(graph.predecessors(s)); - } - } - } - } - return defStmts; - } + /** Returns a copy with a new name, preserving all other state and capabilities. */ + @NonNull Local withName(@NonNull String name); - public List getStmtsUsingOrDefiningthisLocal(Collection stmts, Stmt removedStmt) { - List localOccurrences = new ArrayList<>(); - for (Stmt stmt : stmts) { - if (stmt.equivTo(removedStmt)) continue; - List stmtUsesAndDefs = stmt.getUsesAndDefs(); - for (Value stmtUse : stmtUsesAndDefs) { - if (stmtUse instanceof Local && stmtUse.equivTo(this)) { - localOccurrences.add(stmt); - } - } - } - return localOccurrences; - } + /** Returns a copy with a new type, preserving all other state and capabilities. */ + @NonNull Local withType(@NonNull Type type); - @Override - public V accept(@NonNull V v) { - v.caseLocal(this); - return v; - } + List getDefs(Collection defs); - @NonNull - public Local withName(@NonNull String name) { - return new Local(name, type); - } + List getDefsForLocalUse(ControlFlowGraph graph, Stmt stmt); - @NonNull - public Local withType(@NonNull Type type) { - return new Local(name, type); - } + List getStmtsUsingOrDefiningthisLocal(Collection stmts, Stmt removedStmt); } diff --git a/sootup.core/src/main/java/sootup/core/jimple/common/SlotLocal.java b/sootup.core/src/main/java/sootup/core/jimple/common/SlotLocal.java new file mode 100644 index 00000000000..5ccaf03e4ed --- /dev/null +++ b/sootup.core/src/main/java/sootup/core/jimple/common/SlotLocal.java @@ -0,0 +1,53 @@ +package sootup.core.jimple.common; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 1999-2020 Patrick Lam, Linghui Luo, Markus Schmidt and others + * %% + * 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 org.jspecify.annotations.NonNull; +import sootup.core.types.Type; + +/** + * A local originating from a JVM local variable slot. The index is original bytecode provenance. + */ +public interface SlotLocal extends Local { + /** Returns the original, nonnegative JVM local variable slot index. */ + int getSlotIndex(); + + /** Returns a copy with a nonnegative slot index, preserving all other state and capabilities. */ + @NonNull SlotLocal withSlotIndex(int slotIndex); + + @Override + @NonNull SlotLocal withName(@NonNull String name); + + @Override + @NonNull SlotLocal withType(@NonNull Type type); + + /** Alias for {@link #getSlotIndex()}. */ + default int getIndex() { + return getSlotIndex(); + } + + /** Alias for {@link #withSlotIndex(int)}. */ + default @NonNull SlotLocal withIndex(int slotIndex) { + return withSlotIndex(slotIndex); + } +} diff --git a/sootup.core/src/main/java/sootup/core/jimple/common/StackLocal.java b/sootup.core/src/main/java/sootup/core/jimple/common/StackLocal.java new file mode 100644 index 00000000000..85b4d08deee --- /dev/null +++ b/sootup.core/src/main/java/sootup/core/jimple/common/StackLocal.java @@ -0,0 +1,35 @@ +package sootup.core.jimple.common; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 1999-2020 Patrick Lam, Linghui Luo, Markus Schmidt and others + * %% + * 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 org.jspecify.annotations.NonNull; +import sootup.core.types.Type; + +/** A temporary materialized from the JVM operand stack, with no JVM local variable slot. */ +public interface StackLocal extends Local { + @Override + @NonNull StackLocal withName(@NonNull String name); + + @Override + @NonNull StackLocal withType(@NonNull Type type); +} diff --git a/sootup.core/src/main/java/sootup/core/util/JimpleBodySerializer.java b/sootup.core/src/main/java/sootup/core/util/JimpleBodySerializer.java index 989158be288..6cfac9970a3 100644 --- a/sootup.core/src/main/java/sootup/core/util/JimpleBodySerializer.java +++ b/sootup.core/src/main/java/sootup/core/util/JimpleBodySerializer.java @@ -26,6 +26,8 @@ import sootup.core.graph.ControlFlowGraph; import sootup.core.jimple.common.Immediate; import sootup.core.jimple.common.Local; +import sootup.core.jimple.common.SlotLocal; +import sootup.core.jimple.common.StackLocal; import sootup.core.jimple.common.constant.*; import sootup.core.jimple.common.expr.*; import sootup.core.jimple.common.ref.*; @@ -67,13 +69,22 @@ public static String serialize(ControlFlowGraph cfg, Set locals) { for (Local local : locals) { String varName = toLocalVarName(local.getName(), ctx.usedNames); ctx.localVarNames.put(local, varName); + String factory = + local instanceof SlotLocal + ? "newSlotLocal" + : local instanceof StackLocal ? "newStackLocal" : "newLocal"; sb.append("Local ") .append(varName) - .append(" = new Local(\"") + .append(" = Jimple.") + .append(factory) + .append("(\"") .append(escapeString(local.getName())) .append("\", ") - .append(serializeType(local.getType())) - .append(");\n"); + .append(serializeType(local.getType())); + if (local instanceof SlotLocal) { + sb.append(", ").append(((SlotLocal) local).getSlotIndex()); + } + sb.append(");\n"); } sb.append("\n"); diff --git a/sootup.core/src/test/java/sootup/core/TestUtil.java b/sootup.core/src/test/java/sootup/core/TestUtil.java index 8920bf389b1..991e24e10ea 100644 --- a/sootup.core/src/test/java/sootup/core/TestUtil.java +++ b/sootup.core/src/test/java/sootup/core/TestUtil.java @@ -1,6 +1,7 @@ package sootup.core; import java.util.Collections; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.SimpleStmtPositionInfo; import sootup.core.jimple.common.LValue; import sootup.core.jimple.common.Local; @@ -81,7 +82,7 @@ public static FieldSubSignature createDummyFieldSubSignature() { * @return a dummy Local for an Object */ public static Local createDummyLocalForObject() { - return new Local("a", TestUtil.createDummyClassType()); + return Jimple.newLocal("a", TestUtil.createDummyClassType()); } /** @@ -90,7 +91,7 @@ public static Local createDummyLocalForObject() { * @return a dummy Local for a int value */ public static Local createDummyLocalForInt() { - return new Local("b", IntType.getInstance()); + return Jimple.newLocal("b", IntType.getInstance()); } /** diff --git a/sootup.core/src/test/java/sootup/core/graph/ImmutableBlockControlFlowGraphTest.java b/sootup.core/src/test/java/sootup/core/graph/ImmutableBlockControlFlowGraphTest.java index 714de2696ab..af1eebc7fac 100644 --- a/sootup.core/src/test/java/sootup/core/graph/ImmutableBlockControlFlowGraphTest.java +++ b/sootup.core/src/test/java/sootup/core/graph/ImmutableBlockControlFlowGraphTest.java @@ -4,6 +4,7 @@ import java.util.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.constant.IntConstant; @@ -54,7 +55,7 @@ private MutableBlockControlFlowGraph buildLinearGraph() { } private MutableBlockControlFlowGraph buildBranchingGraph() { - Local l = new Local("i", PrimitiveType.IntType.getInstance()); + Local l = Jimple.newLocal("i", PrimitiveType.IntType.getInstance()); MutableBlockControlFlowGraph g = new MutableBlockControlFlowGraph(); JNopStmt entry = new JNopStmt(noPosInfo); @@ -246,7 +247,7 @@ void exceptionalSuccessors_preservedFromMutableGraph() { MutableBlockControlFlowGraph mutable = new MutableBlockControlFlowGraph(); JReturnVoidStmt body = new JReturnVoidStmt(noPosInfo); - Local caughtLocal = new Local("$e", throwableType); + Local caughtLocal = Jimple.newLocal("$e", throwableType); JIdentityStmt catchHandler = new JIdentityStmt(caughtLocal, new JCaughtExceptionRef(throwableType), noPosInfo); JReturnVoidStmt catchRet = new JReturnVoidStmt(noPosInfo); diff --git a/sootup.core/src/test/java/sootup/core/graph/MutableBlockControlFlowGraphTest.java b/sootup.core/src/test/java/sootup/core/graph/MutableBlockControlFlowGraphTest.java index bd70b621db4..90301c7843f 100644 --- a/sootup.core/src/test/java/sootup/core/graph/MutableBlockControlFlowGraphTest.java +++ b/sootup.core/src/test/java/sootup/core/graph/MutableBlockControlFlowGraphTest.java @@ -5,6 +5,7 @@ import java.util.*; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Trap; @@ -73,12 +74,12 @@ public PackageName getPackageName() { Stmt firstHandlerStmt = new JIdentityStmt( - new Local("ex", throwableSig), + Jimple.newLocal("ex", throwableSig), new JCaughtExceptionRef(throwableSig), StmtPositionInfo.getNoStmtPositionInfo()); Stmt secondHandlerStmt = new JIdentityStmt( - new Local("ex2", throwableSig), + Jimple.newLocal("ex2", throwableSig), new JCaughtExceptionRef(ioExceptionSig), StmtPositionInfo.getNoStmtPositionInfo()); @@ -592,7 +593,7 @@ public PackageName getPackageName() { } }; - Local exc = new Local("ex", UnknownType.getInstance()); + Local exc = Jimple.newLocal("ex", UnknownType.getInstance()); // hint: applied types make no sense in this test! Stmt catchStmt1 = new JIdentityStmt( @@ -1046,7 +1047,7 @@ public void testRemoveSingleTrap() { JGotoStmt stmt2 = new JGotoStmt(StmtPositionInfo.getNoStmtPositionInfo()); Stmt handlerStmt = new JIdentityStmt( - new Local("ex", throwableSig), + Jimple.newLocal("ex", throwableSig), new JCaughtExceptionRef(throwableSig), StmtPositionInfo.getNoStmtPositionInfo()); @@ -1084,13 +1085,13 @@ public void testRemoveMultipleTrapsWithDifferentExceptionTypes() { Stmt handlerStmt1 = new JIdentityStmt( - new Local("ex1", throwableSig), + Jimple.newLocal("ex1", throwableSig), new JCaughtExceptionRef(throwableSig), StmtPositionInfo.getNoStmtPositionInfo()); Stmt handlerStmt2 = new JIdentityStmt( - new Local("ex2", ioExceptionSig), + Jimple.newLocal("ex2", ioExceptionSig), new JCaughtExceptionRef(ioExceptionSig), StmtPositionInfo.getNoStmtPositionInfo()); @@ -1131,13 +1132,13 @@ public void testGetEntrypoints() { Stmt handlerStmt1 = new JIdentityStmt( - new Local("ex1", throwableSig), + Jimple.newLocal("ex1", throwableSig), new JCaughtExceptionRef(throwableSig), StmtPositionInfo.getNoStmtPositionInfo()); Stmt handlerStmt2 = new JIdentityStmt( - new Local("ex2", ioExceptionSig), + Jimple.newLocal("ex2", ioExceptionSig), new JCaughtExceptionRef(ioExceptionSig), StmtPositionInfo.getNoStmtPositionInfo()); diff --git a/sootup.core/src/test/java/sootup/core/graph/TestGraphGenerator.java b/sootup.core/src/test/java/sootup/core/graph/TestGraphGenerator.java index 2170f8f0d83..f412d63ccfd 100644 --- a/sootup.core/src/test/java/sootup/core/graph/TestGraphGenerator.java +++ b/sootup.core/src/test/java/sootup/core/graph/TestGraphGenerator.java @@ -1,5 +1,6 @@ package sootup.core.graph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.constant.IntConstant; @@ -17,9 +18,9 @@ public class TestGraphGenerator { public MutableBlockControlFlowGraph createControlFlowGraph() { MutableBlockControlFlowGraph graph = new MutableBlockControlFlowGraph(); - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); - Local l2 = new Local("l2", PrimitiveType.IntType.getInstance()); - Local l3 = new Local("l3", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); + Local l2 = Jimple.newLocal("l2", PrimitiveType.IntType.getInstance()); + Local l3 = Jimple.newLocal("l3", PrimitiveType.IntType.getInstance()); JAssignStmt assign01 = new JAssignStmt(l1, IntConstant.getInstance(1), noPosInfo); JAssignStmt assign02 = new JAssignStmt(l2, IntConstant.getInstance(2), noPosInfo); @@ -73,9 +74,9 @@ public MutableBlockControlFlowGraph createControlFlowGraph() { public MutableBlockControlFlowGraph createControlFlowGraph2() { MutableBlockControlFlowGraph graph = new MutableBlockControlFlowGraph(); - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); - Local l2 = new Local("l2", PrimitiveType.IntType.getInstance()); - Local l3 = new Local("l3", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); + Local l2 = Jimple.newLocal("l2", PrimitiveType.IntType.getInstance()); + Local l3 = Jimple.newLocal("l3", PrimitiveType.IntType.getInstance()); JAssignStmt assignl1e1 = new JAssignStmt(l1, IntConstant.getInstance(1), noPosInfo); JAssignStmt assignl2e1 = new JAssignStmt(l2, IntConstant.getInstance(1), noPosInfo); @@ -131,9 +132,9 @@ public MutableBlockControlFlowGraph createControlFlowGraph2() { public MutableBlockControlFlowGraph createControlFlowGraph3() { MutableBlockControlFlowGraph graph = new MutableBlockControlFlowGraph(); - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); - Local l2 = new Local("l2", PrimitiveType.IntType.getInstance()); - Local l3 = new Local("l3", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); + Local l2 = Jimple.newLocal("l2", PrimitiveType.IntType.getInstance()); + Local l3 = Jimple.newLocal("l3", PrimitiveType.IntType.getInstance()); JAssignStmt assignl1e1 = new JAssignStmt(l1, IntConstant.getInstance(1), noPosInfo); JAssignStmt assignl2e1 = new JAssignStmt(l2, IntConstant.getInstance(2), noPosInfo); diff --git a/sootup.core/src/test/java/sootup/core/jimple/common/LocalTest.java b/sootup.core/src/test/java/sootup/core/jimple/common/LocalTest.java index 2da2f7fc5f5..ebe93930b30 100644 --- a/sootup.core/src/test/java/sootup/core/jimple/common/LocalTest.java +++ b/sootup.core/src/test/java/sootup/core/jimple/common/LocalTest.java @@ -1,11 +1,11 @@ package sootup.core.jimple.common; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import java.util.List; import org.junit.jupiter.api.Test; import sootup.core.graph.MutableBlockControlFlowGraph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.constant.IntConstant; import sootup.core.jimple.common.expr.JAddExpr; @@ -34,7 +34,7 @@ class LocalTest { */ @Test void testGetDefsForLocalUseExcludesSelfDefinition() { - Local x = new Local("x", PrimitiveType.getInt()); + Local x = Jimple.newLocal("x", PrimitiveType.getInt()); JAssignStmt s1 = new JAssignStmt(x, IntConstant.getInstance(5), StmtPositionInfo.getNoStmtPositionInfo()); JAssignStmt s2 = @@ -55,8 +55,8 @@ void testGetDefsForLocalUseExcludesSelfDefinition() { @Test void testGetDefsForLocalUseBranches() { - Local x = new Local("x", PrimitiveType.getInt()); - Local y = new Local("y", PrimitiveType.getInt()); + Local x = Jimple.newLocal("x", PrimitiveType.getInt()); + Local y = Jimple.newLocal("y", PrimitiveType.getInt()); JAssignStmt s1 = new JAssignStmt(x, IntConstant.getInstance(5), StmtPositionInfo.getNoStmtPositionInfo()); JGotoStmt goto1 = new JGotoStmt(StmtPositionInfo.getNoStmtPositionInfo()); @@ -84,7 +84,7 @@ void testGetDefsForLocalUseBranches() { @Test void testGetDefsForLocalUseReachesSelfThroughLoop() { - Local i = new Local("i", PrimitiveType.getInt()); + Local i = Jimple.newLocal("i", PrimitiveType.getInt()); StmtPositionInfo pos = StmtPositionInfo.getNoStmtPositionInfo(); JAssignStmt init = new JAssignStmt(i, IntConstant.getInstance(0), pos); JIfStmt cond = new JIfStmt(new JGtExpr(i, IntConstant.getInstance(5)), pos); @@ -107,4 +107,88 @@ void testGetDefsForLocalUseReachesSelfThroughLoop() { assertTrue(defs.contains(init)); assertTrue(defs.contains(step)); } + + @Test + void testLocalSlotIndexWithers() { + SlotLocal local = Jimple.newSlotLocal("a", PrimitiveType.getInt(), 3); + assertEquals(3, local.getSlotIndex()); + assertEquals(3, local.getIndex()); + assertEquals(5, local.withSlotIndex(5).getSlotIndex()); + assertEquals(8, local.withIndex(8).getSlotIndex()); + assertEquals(3, local.withName("b").getSlotIndex()); + assertEquals("b", local.withName("b").getName()); + assertEquals(3, local.withType(PrimitiveType.getFloat()).getSlotIndex()); + assertEquals(PrimitiveType.getFloat(), local.withType(PrimitiveType.getFloat()).getType()); + assertEquals(3, local.getSlotIndex()); + } + + @Test + void testStackWithersPreserveCategory() { + Local stack = Jimple.newStackLocal("temporary", PrimitiveType.getInt()); + assertInstanceOf(StackLocal.class, stack.withName("renamed")); + assertInstanceOf(StackLocal.class, stack.withType(PrimitiveType.getFloat())); + assertFalse(stack instanceof SlotLocal); + assertFalse(Jimple.newLocal("generic", PrimitiveType.getInt()) instanceof StackLocal); + } + + @Test + void testEqualityAcrossCategories() { + Local generic = Jimple.newLocal("same", PrimitiveType.getInt()); + Local stack = Jimple.newStackLocal("same", PrimitiveType.getInt()); + Local slot = Jimple.newSlotLocal("same", PrimitiveType.getInt(), 2); + for (Local left : List.of(generic, stack, slot)) { + for (Local right : List.of(generic, stack, slot)) { + assertEquals(left, right); + assertEquals(left.hashCode(), right.hashCode()); + assertTrue(left.equivTo(right)); + assertEquals(left.equivHashCode(), right.equivHashCode()); + } + } + assertEquals(1, new java.util.HashSet<>(List.of(generic, stack, slot)).size()); + var map = new java.util.HashMap(); + map.put(slot, "value"); + assertEquals("value", map.get(stack)); + assertEquals(generic, slot.withType(PrimitiveType.getFloat())); + assertFalse(generic.equivTo(slot.withType(PrimitiveType.getFloat()))); + assertNotEquals(generic, slot.withName("other")); + } + + @Test + void testVisitorDispatch() { + for (Local local : + List.of( + Jimple.newLocal("generic", PrimitiveType.getInt()), + Jimple.newStackLocal("stack", PrimitiveType.getInt()), + Jimple.newSlotLocal("slot", PrimitiveType.getInt(), 0))) { + var visited = new java.util.ArrayList(); + var visitor = + new sootup.core.jimple.visitor.AbstractImmediateVisitor() { + @Override + public void caseLocal(Local value) { + visited.add(value); + } + }; + assertSame(visitor, local.accept(visitor)); + assertEquals(List.of(local), visited); + } + } + + @Test + @SuppressWarnings("deprecation") + void testSlotValidationAndLegacyFactories() { + assertInstanceOf(SlotLocal.class, Jimple.newLocal("a", PrimitiveType.getInt(), 0)); + Local generic = Jimple.newLocal("a", PrimitiveType.getInt(), -1); + assertFalse(generic instanceof SlotLocal); + assertFalse(generic instanceof StackLocal); + assertThrows( + IllegalArgumentException.class, () -> Jimple.newLocal("a", PrimitiveType.getInt(), -2)); + assertThrows( + IllegalArgumentException.class, () -> Jimple.newSlotLocal("a", PrimitiveType.getInt(), -1)); + assertThrows( + IllegalArgumentException.class, + () -> Jimple.newSlotLocal("a", PrimitiveType.getInt(), 0).withSlotIndex(-1)); + assertThrows( + RuntimeException.class, + () -> Jimple.newStackLocal("void", sootup.core.types.VoidType.getInstance())); + } } diff --git a/sootup.core/src/test/java/sootup/core/util/JimpleBodySerializerTest.java b/sootup.core/src/test/java/sootup/core/util/JimpleBodySerializerTest.java index 36e8c3e468a..b8e165fbaab 100644 --- a/sootup.core/src/test/java/sootup/core/util/JimpleBodySerializerTest.java +++ b/sootup.core/src/test/java/sootup/core/util/JimpleBodySerializerTest.java @@ -24,11 +24,17 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import javax.tools.ToolProvider; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import sootup.core.graph.MutableBlockControlFlowGraph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.constant.IntConstant; @@ -43,7 +49,7 @@ public class JimpleBodySerializerTest { @Test public void testLinearBody() { - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); JAssignStmt assign = new JAssignStmt(l1, IntConstant.getInstance(42), NO_POS); JReturnStmt ret = new JReturnStmt(l1, NO_POS); @@ -55,7 +61,8 @@ public void testLinearBody() { String code = JimpleBodySerializer.serialize(graph, locals); assertTrue( - code.contains("Local local_l1 = new Local(\"l1\", PrimitiveType.IntType.getInstance())")); + code.contains( + "Local local_l1 = Jimple.newLocal(\"l1\", PrimitiveType.IntType.getInstance())")); assertTrue( code.contains( "JAssignStmt assignStmt_0 = new JAssignStmt(local_l1, IntConstant.getInstance(42), noPos)")); @@ -66,7 +73,7 @@ public void testLinearBody() { @Test public void testIfElseBody() { - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); JAssignStmt assign = new JAssignStmt(l1, IntConstant.getInstance(0), NO_POS); JIfStmt ifStmt = new JIfStmt(new JEqExpr(l1, IntConstant.getInstance(0)), NO_POS); JReturnStmt retTrue = new JReturnStmt(IntConstant.getInstance(1), NO_POS); @@ -91,7 +98,7 @@ public void testIfElseBody() { @Test public void testLoopWithGoto() { - Local l1 = new Local("l1", PrimitiveType.IntType.getInstance()); + Local l1 = Jimple.newLocal("l1", PrimitiveType.IntType.getInstance()); JAssignStmt init = new JAssignStmt(l1, IntConstant.getInstance(0), NO_POS); JAssignStmt inc = new JAssignStmt(l1, new JAddExpr(l1, IntConstant.getInstance(1)), NO_POS); JGotoStmt gotoStmt = new JGotoStmt(NO_POS); @@ -152,4 +159,59 @@ public void testTypeSerializationPrimitiveTypes() { "PrimitiveType.DoubleType.getInstance()", JimpleBodySerializer.serializeType(PrimitiveType.DoubleType.getInstance())); } + + @Test + public void serializedProvenanceCompiles(@TempDir Path directory) throws Exception { + Local generic = Jimple.newLocal("generic", PrimitiveType.getInt()); + Local stack = Jimple.newStackLocal("stack", PrimitiveType.getInt()); + Local slot = Jimple.newSlotLocal("slot", PrimitiveType.getInt(), 4); + var ret = new JReturnVoidStmt(NO_POS); + var graph = new MutableBlockControlFlowGraph(); + graph.addNode(ret); + graph.setStartingStmt(ret); + String code = + JimpleBodySerializer.serialize(graph, new LinkedHashSet<>(List.of(generic, stack, slot))); + assertTrue( + code.contains("Jimple.newStackLocal(\"stack\", PrimitiveType.IntType.getInstance())")); + assertTrue( + code.contains("Jimple.newSlotLocal(\"slot\", PrimitiveType.IntType.getInstance(), 4)")); + String source = + """ + import java.util.*; + import sootup.core.jimple.Jimple; + import sootup.core.jimple.basic.*; + import sootup.core.jimple.common.*; + import sootup.core.jimple.common.stmt.*; + import sootup.core.graph.*; + import sootup.core.types.*; + import sootup.core.model.*; + import sootup.core.signatures.MethodSignature; + public class SerializedBody { + public static Body build(MethodSignature signature) { + """ + + code.replace("/* provide MethodSignature here */", "signature") + + "return body; } }"; + Path file = directory.resolve("SerializedBody.java"); + Files.writeString(file, source); + var compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull(compiler); + var diagnostics = new javax.tools.DiagnosticCollector(); + try (var manager = compiler.getStandardFileManager(diagnostics, null, null)) { + assertTrue( + compiler + .getTask( + null, + manager, + diagnostics, + List.of( + "-classpath", + System.getProperty("java.class.path"), + "-d", + directory.toString()), + null, + manager.getJavaFileObjects(file.toFile())) + .call(), + diagnostics.getDiagnostics().toString()); + } + } } diff --git a/sootup.examples/src/test/java/sootup/examples/taintAnalysis/TaintAnalysisTest.java b/sootup.examples/src/test/java/sootup/examples/taintAnalysis/TaintAnalysisTest.java index 62316de59f3..8c88d625b25 100644 --- a/sootup.examples/src/test/java/sootup/examples/taintAnalysis/TaintAnalysisTest.java +++ b/sootup.examples/src/test/java/sootup/examples/taintAnalysis/TaintAnalysisTest.java @@ -19,8 +19,8 @@ import sootup.analysis.interprocedural.ifds.DefaultJimpleIFDSTabulationProblem; import sootup.analysis.interprocedural.ifds.JimpleIFDSSolver; import sootup.core.inputlocation.AnalysisInputLocation; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Immediate; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Value; import sootup.core.jimple.common.constant.StringConstant; import sootup.core.jimple.common.expr.AbstractInvokeExpr; @@ -138,7 +138,7 @@ public Map> initialSeeds() { // --8<-- [start:zero-value] @Override protected Value createZeroValue() { - return new Local("<>", NullType.getInstance()); + return Jimple.newLocal("<>", NullType.getInstance()); } // --8<-- [end:zero-value] diff --git a/sootup.examples/src/test/java/sootup/examples/typestate/TypestateProblem.java b/sootup.examples/src/test/java/sootup/examples/typestate/TypestateProblem.java index 92e651f19aa..6f7f5cbf1e8 100644 --- a/sootup.examples/src/test/java/sootup/examples/typestate/TypestateProblem.java +++ b/sootup.examples/src/test/java/sootup/examples/typestate/TypestateProblem.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import sootup.analysis.interprocedural.ide.DefaultJimpleIDETabulationProblem; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Immediate; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Value; @@ -70,7 +71,7 @@ public TypestateProblem( */ @Override protected Value createZeroValue() { - return new Local("<>", NullType.getInstance()); + return Jimple.newLocal("<>", NullType.getInstance()); } /** Where the analysis starts: the zero fact, at the first statement of the entry method. */ diff --git a/sootup.interceptors/src/main/java/sootup/interceptors/LocalNameStandardizer.java b/sootup.interceptors/src/main/java/sootup/interceptors/LocalNameStandardizer.java index de7a828daaf..5b528471bb1 100644 --- a/sootup.interceptors/src/main/java/sootup/interceptors/LocalNameStandardizer.java +++ b/sootup.interceptors/src/main/java/sootup/interceptors/LocalNameStandardizer.java @@ -67,7 +67,9 @@ public void interceptBody(Body.@NonNull BodyBuilder builder, @NonNull View view) Local local = iterator.next(); Local newLocal; Type type = local.getType(); - newLocal = lgen.generateLocal(type); + // Generate a standard name, then copy the original local with that name to preserve its kind, + // JVM slot index, and Java annotations. + newLocal = local.withName(lgen.generateLocal(type).getName()); builder.replaceLocal(local, newLocal); } } diff --git a/sootup.interceptors/src/test/java/sootup/interceptors/LocalProvenanceTest.java b/sootup.interceptors/src/test/java/sootup/interceptors/LocalProvenanceTest.java new file mode 100644 index 00000000000..ced192ff580 --- /dev/null +++ b/sootup.interceptors/src/test/java/sootup/interceptors/LocalProvenanceTest.java @@ -0,0 +1,249 @@ +package sootup.interceptors; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import sootup.core.graph.MutableBlockControlFlowGraph; +import sootup.core.jimple.Jimple; +import sootup.core.jimple.basic.StmtPositionInfo; +import sootup.core.jimple.common.Local; +import sootup.core.jimple.common.SlotLocal; +import sootup.core.jimple.common.StackLocal; +import sootup.core.jimple.common.constant.IntConstant; +import sootup.core.jimple.common.stmt.*; +import sootup.core.model.Body; +import sootup.core.types.PrimitiveType; +import sootup.core.types.UnknownType; +import sootup.interceptors.typeresolving.TypeResolver; +import sootup.java.core.AnnotationUsage; +import sootup.java.core.JavaIdentifierFactory; +import sootup.java.core.jimple.basic.JavaLocal; +import sootup.java.core.language.JavaJimple; +import sootup.java.core.views.JavaView; + +/** + * Verifies local kind, original JVM slot index, and Java annotation handling during packing, + * splitting, type resolution, and name standardization. + */ +class LocalProvenanceTest { + private static final StmtPositionInfo NO_POS = StmtPositionInfo.getNoStmtPositionInfo(); + private final JavaView view = new JavaView(Collections.emptyList()); + + private Body.BodyBuilder body(List locals, List stmts) { + var graph = new MutableBlockControlFlowGraph(); + for (int i = 1; i < stmts.size(); i++) { + graph.putEdge((FallsThroughStmt) stmts.get(i - 1), stmts.get(i)); + } + graph.setStartingStmt(stmts.get(0)); + return Body.builder(graph) + .setLocals(new LinkedHashSet<>(locals)) + .setMethodSignature( + new JavaIdentifierFactory() + .getMethodSignature( + new JavaIdentifierFactory().getClassType("example.Test"), + "test", + PrimitiveType.getInt(), + List.of())); + } + + private Body.BodyBuilder disjoint(Local first, Local second) { + return body( + List.of(first, second), + List.of( + Jimple.newAssignStmt(first, IntConstant.getInstance(1), NO_POS), + Jimple.newAssignStmt( + first, Jimple.newAddExpr(first, IntConstant.getInstance(1)), NO_POS), + Jimple.newAssignStmt(second, IntConstant.getInstance(2), NO_POS), + Jimple.newReturnStmt(second, NO_POS))); + } + + /** Supplies a parameter color so packing checks do not depend on allocating a new color. */ + private Body.BodyBuilder disjointWithParameter(Local first, Local second) { + Local parameter = Jimple.newLocal("parameter", PrimitiveType.getInt()); + var builder = + body( + List.of(first, second, parameter), + List.of( + Jimple.newIdentityStmt( + parameter, Jimple.newParameterRef(PrimitiveType.getInt(), 0), NO_POS), + Jimple.newAssignStmt( + first, Jimple.newAddExpr(parameter, IntConstant.getInstance(1)), NO_POS), + Jimple.newAssignStmt( + second, Jimple.newAddExpr(first, IntConstant.getInstance(1)), NO_POS), + Jimple.newReturnStmt(second, NO_POS))); + var factory = new JavaIdentifierFactory(); + return builder.setMethodSignature( + factory.getMethodSignature( + factory.getClassType("example.Test"), + "test", + PrimitiveType.getInt(), + List.of(PrimitiveType.getInt()))); + } + + /** + * Packing locals with disjoint lifetimes preserves the representative's implementation, slot + * index, and annotation collection. + */ + @Test + void packsNonInterferingLocalsAndPreservesRepresentativeState() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + List> factories = + List.of( + name -> Jimple.newLocal(name, PrimitiveType.getInt()), + name -> Jimple.newStackLocal(name, PrimitiveType.getInt()), + name -> Jimple.newSlotLocal(name, PrimitiveType.getInt(), 3), + name -> JavaJimple.newLocal(name, PrimitiveType.getInt(), annotations), + name -> JavaJimple.newStackLocal(name, PrimitiveType.getInt(), annotations), + name -> JavaJimple.newSlotLocal(name, PrimitiveType.getInt(), 3, annotations)); + for (var factory : factories) { + Local first = factory.apply("first"); + var builder = disjointWithParameter(first, factory.apply("second")); + new LocalPacker().interceptBody(builder, view); + assertEquals(1, builder.getLocals().size()); + Local packed = builder.getLocals().iterator().next(); + assertSame(first.getClass(), packed.getClass()); + if (packed instanceof JavaLocal) { + assertSame(annotations, ((JavaLocal) packed).getAnnotations()); + } + if (packed instanceof SlotLocal) { + assertEquals(3, ((SlotLocal) packed).getSlotIndex()); + } + assertEquals(List.of(packed), builder.getStmts().get(3).getUses()); + } + } + + /** + * Locals with disjoint lifetimes merge across kinds and slots, retaining the first + * representative's kind and slot index while rewriting all definitions and uses. + */ + @Test + void packsAcrossKindsAndDifferentSlots() { + Local generic = Jimple.newLocal("generic", PrimitiveType.getInt()); + Local stack = Jimple.newStackLocal("stack", PrimitiveType.getInt()); + Local slot0 = Jimple.newSlotLocal("slot0", PrimitiveType.getInt(), 0); + Local slot1 = Jimple.newSlotLocal("slot1", PrimitiveType.getInt(), 1); + List locals = List.of(generic, stack, slot0, slot1); + for (int i = 0; i < locals.size(); i++) { + for (int j = 0; j < locals.size(); j++) { + if (i == j) { + continue; + } + var builder = disjointWithParameter(locals.get(i), locals.get(j)); + new LocalPacker().interceptBody(builder, view); + assertEquals(1, builder.getLocals().size()); + Local packed = builder.getLocals().iterator().next(); + assertSame(locals.get(i).getClass(), packed.getClass()); + if (packed instanceof SlotLocal) { + assertEquals( + ((SlotLocal) locals.get(i)).getSlotIndex(), ((SlotLocal) packed).getSlotIndex()); + } + for (Stmt stmt : builder.getStmts()) { + stmt.getDef().ifPresent(def -> assertSame(packed, def)); + stmt.getUses().stream() + .filter(Local.class::isInstance) + .forEach(use -> assertSame(packed, use)); + } + } + } + } + + /** + * Addition operands stay distinct across all kind and slot combinations; the result reuses an + * operand's local once the operands are dead. + */ + @Test + void keepsInterferingLocalsSeparateAcrossKindsAndSlots() { + List> factories = + List.of( + name -> Jimple.newLocal(name, PrimitiveType.getInt()), + name -> Jimple.newStackLocal(name, PrimitiveType.getInt()), + name -> Jimple.newSlotLocal(name, PrimitiveType.getInt(), 3), + name -> Jimple.newSlotLocal(name, PrimitiveType.getInt(), 4)); + for (var firstFactory : factories) { + for (var secondFactory : factories) { + Local first = firstFactory.apply("first"); + Local second = secondFactory.apply("second"); + Local result = Jimple.newStackLocal("result", PrimitiveType.getInt()); + var builder = + body( + List.of(first, second, result), + List.of( + Jimple.newAssignStmt(first, IntConstant.getInstance(1), NO_POS), + Jimple.newAssignStmt(second, IntConstant.getInstance(2), NO_POS), + Jimple.newAssignStmt(result, Jimple.newAddExpr(first, second), NO_POS), + Jimple.newReturnStmt(result, NO_POS))); + new LocalPacker().interceptBody(builder, view); + assertEquals(2, builder.getLocals().size()); + var sum = (JAssignStmt) builder.getStmts().get(2); + assertEquals(2, sum.getUses().stream().filter(Local.class::isInstance).distinct().count()); + } + } + } + + /** + * Splitting repeated assignments and resolving unknown types preserves each Java stack or slot + * local's kind, original slot index, and annotation collection. + */ + @Test + void splitterAndTypeResolverPreserveJavaProvenance() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + List originals = + List.of( + JavaJimple.newSlotLocal("slot", UnknownType.getInstance(), 4, annotations), + JavaJimple.newStackLocal("stack", UnknownType.getInstance(), annotations)); + for (JavaLocal original : originals) { + var builder = disjoint(original, original); + new LocalSplitter().interceptBody(builder, view); + assertEquals(3, builder.getLocals().size()); + assertTrue(new TypeResolver(view).resolve(builder)); + for (Local local : builder.getLocals()) { + assertSame(original.getClass(), local.getClass()); + assertNotEquals(UnknownType.getInstance(), local.getType()); + assertSame(annotations, ((JavaLocal) local).getAnnotations()); + if (local instanceof SlotLocal) { + assertEquals(4, ((SlotLocal) local).getSlotIndex()); + } + } + } + } + + /** + * Name standardization renames Java stack and slot locals while preserving their kinds, slot + * indices, and annotation collection. + */ + @Test + void nameStandardizerPreservesJavaProvenance() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + Local slot = JavaJimple.newSlotLocal("slot", PrimitiveType.getInt(), 4, annotations); + Local stack = JavaJimple.newStackLocal("stack", PrimitiveType.getInt(), annotations); + var builder = disjoint(slot, stack); + new LocalNameStandardizer().interceptBody(builder, view); + assertEquals(2, builder.getLocals().size()); + assertEquals(1, builder.getLocals().stream().filter(StackLocal.class::isInstance).count()); + SlotLocal renamed = + (SlotLocal) + builder.getLocals().stream() + .filter(SlotLocal.class::isInstance) + .findFirst() + .orElseThrow(); + assertEquals(4, renamed.getSlotIndex()); + for (Local local : builder.getLocals()) { + assertSame(annotations, ((JavaLocal) local).getAnnotations()); + assertFalse(List.of("slot", "stack").contains(local.getName())); + } + } +} diff --git a/sootup.java.bytecode.frontend/src/main/java/sootup/java/bytecode/frontend/conversion/AsmMethodSource.java b/sootup.java.bytecode.frontend/src/main/java/sootup/java/bytecode/frontend/conversion/AsmMethodSource.java index c6501fa21ea..87e5435a6b1 100644 --- a/sootup.java.bytecode.frontend/src/main/java/sootup/java/bytecode/frontend/conversion/AsmMethodSource.java +++ b/sootup.java.bytecode.frontend/src/main/java/sootup/java/bytecode/frontend/conversion/AsmMethodSource.java @@ -338,13 +338,16 @@ private JavaLocal getOrCreateLocal( // the name is collision-free already (see LocalVariableTableLocals) return lvtLocals.getOrCreate( lvn, - name -> JavaJimple.newLocal(name, type, resolveLocalVariableAnnotations(idx, lvn.start))); + name -> + JavaJimple.newSlotLocal( + name, type, idx, resolveLocalVariableAnnotations(idx, lvn.start))); } JavaLocal local = locals.get(idx); if (local == null) { String nameCandidate = determineLocalName(idx, atInsn); - local = createUniqueLocal(nameCandidate, type, resolveLocalVariableAnnotations(idx, atInsn)); + local = + createUniqueLocal(nameCandidate, type, idx, resolveLocalVariableAnnotations(idx, atInsn)); locals.set(idx, local); } return local; @@ -386,15 +389,12 @@ private String determineLocalName(int idx, @Nullable AbstractInsnNode atInsn) { return "l" + idx; } - private JavaLocal createUniqueLocal(@NonNull String nameCandidate, @NonNull Type type) { - return createUniqueLocal(nameCandidate, type, Collections.emptyList()); - } - private JavaLocal createUniqueLocal( @NonNull String nameCandidate, @NonNull Type type, + int slotIndex, @NonNull List annotations) { - return JavaJimple.newLocal(uniqueLocalName(nameCandidate), type, annotations); + return JavaJimple.newSlotLocal(uniqueLocalName(nameCandidate), type, slotIndex, annotations); } /** @@ -505,7 +505,7 @@ void setStmt(@NonNull AbstractInsnNode insn, @NonNull Stmt stmt) { @NonNull Local newStackLocal(@NonNull Type type) { int idx = nextLocal++; - JavaLocal l = createUniqueLocal("$stack" + idx, type); + JavaLocal l = JavaJimple.newStackLocal(uniqueLocalName("$stack" + idx), type); locals.set(idx, l); return l; } @@ -1877,7 +1877,7 @@ private List buildPreambleLocals(Body.BodyBuilder bodyBuilder) { int localIdx = 0; // create this Local if necessary ( i.e. not static ) if (!bodyBuilder.getModifiers().contains(MethodModifier.STATIC)) { - JavaLocal thisLocal = JavaJimple.newLocal("this", declaringClass); + JavaLocal thisLocal = JavaJimple.newSlotLocal("this", declaringClass, localIdx); usedLocalNames.add("this"); LocalVariableNode lvn = lvtLocals.resolvePreamble(localIdx); if (lvn != null) { @@ -1909,11 +1909,12 @@ private List buildPreambleLocals(Body.BodyBuilder bodyBuilder) { collectFormalParameterTypeAnnotations(parameterAnnotations, i); LocalVariableNode lvn = lvtLocals.resolvePreamble(localIdx); JavaLocal local = - JavaJimple.newLocal( + JavaJimple.newSlotLocal( lvn != null ? lvtLocals.nameOf(lvn) : uniqueLocalName(determineLocalName(localIdx, null)), parameterType, + localIdx, parameterAnnotations); if (lvn != null) { lvtLocals.registerPreambleLocal(lvn, local); diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/LocalSlotIndexTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/LocalSlotIndexTest.java new file mode 100644 index 00000000000..0970f5a36d3 --- /dev/null +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/LocalSlotIndexTest.java @@ -0,0 +1,239 @@ +package sootup.java.bytecode.frontend; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import sootup.core.inputlocation.AnalysisInputLocation; +import sootup.core.jimple.common.Local; +import sootup.core.jimple.common.SlotLocal; +import sootup.core.jimple.common.StackLocal; +import sootup.core.jimple.common.ref.JParameterRef; +import sootup.core.jimple.common.ref.JThisRef; +import sootup.core.jimple.common.stmt.JIdentityStmt; +import sootup.core.model.Body; +import sootup.core.model.SourceType; +import sootup.core.types.PrimitiveType; +import sootup.java.bytecode.frontend.inputlocation.JavaClassPathAnalysisInputLocation; +import sootup.java.core.JavaSootMethod; +import sootup.java.core.views.JavaView; + +public class LocalSlotIndexTest { + + private final String directory = "src/test/resources/bugfixes/"; + + @Test + public void testSlotIndicesInStaticMethod() { + AnalysisInputLocation inputLocation = + new JavaClassPathAnalysisInputLocation( + directory, SourceType.Application, Collections.emptyList()); + + JavaView view = new JavaView(inputLocation); + JavaSootMethod method = + view.getClass(view.getIdentifierFactory().getClassType("LocalNamesReusedSlots")) + .get() + .getMethod( + "main", + Collections.singletonList( + view.getIdentifierFactory().getType("java.lang.String[]"))) + .get(); + + Body body = method.getBody(); + Map localsByName = + body.getLocals().stream().collect(Collectors.toMap(Local::getName, l -> l, (a, b) -> a)); + + // Parameter args has slot index 0 in static method + Local argsLocal = localsByName.get("args"); + assertNotNull(argsLocal); + assertEquals(0, assertInstanceOf(SlotLocal.class, argsLocal).getSlotIndex()); + + // Disjoint scope locals reusing slots + Local aLocal = localsByName.get("a"); + assertNotNull(aLocal); + assertEquals(1, assertInstanceOf(SlotLocal.class, aLocal).getSlotIndex()); + + Local bLocal = localsByName.get("b"); + assertNotNull(bLocal); + assertEquals(2, assertInstanceOf(SlotLocal.class, bLocal).getSlotIndex()); + + Local cLocal = localsByName.get("c"); + assertNotNull(cLocal); + assertEquals(1, assertInstanceOf(SlotLocal.class, cLocal).getSlotIndex()); + + Local dLocal = localsByName.get("d"); + assertNotNull(dLocal); + assertEquals(2, assertInstanceOf(SlotLocal.class, dLocal).getSlotIndex()); + + assertTrue(body.getLocals().stream().anyMatch(StackLocal.class::isInstance)); + for (Local local : body.getLocals()) { + assertTrue(local instanceof SlotLocal || local instanceof StackLocal); + if (local instanceof StackLocal) { + assertFalse(local instanceof SlotLocal); + } + } + } + + @Test + public void testSlotIndicesInInstanceMethod() { + AnalysisInputLocation inputLocation = + new JavaClassPathAnalysisInputLocation( + directory, SourceType.Application, Collections.emptyList()); + + JavaView view = new JavaView(inputLocation); + JavaSootMethod method = + view.getClass(view.getIdentifierFactory().getClassType("LocalNamesReusedSlots$Foo")) + .get() + .getMethod( + "use", + Collections.singletonList( + view.getIdentifierFactory().getClassType("LocalNamesReusedSlots$Foo"))) + .get(); + + Body body = method.getBody(); + Map localsByName = + body.getLocals().stream().collect(Collectors.toMap(Local::getName, l -> l, (a, b) -> a)); + + // In instance methods, 'this' occupies slot 0 + Local thisLocal = localsByName.get("this"); + assertNotNull(thisLocal); + assertEquals(0, assertInstanceOf(SlotLocal.class, thisLocal).getSlotIndex()); + + // Formal parameter 'other' occupies slot 1 + Local otherLocal = localsByName.get("other"); + assertNotNull(otherLocal); + assertEquals(1, assertInstanceOf(SlotLocal.class, otherLocal).getSlotIndex()); + + assertTrue(body.getLocals().stream().anyMatch(StackLocal.class::isInstance)); + for (Local local : body.getLocals()) { + assertTrue(local instanceof SlotLocal || local instanceof StackLocal); + if (local instanceof StackLocal) { + assertFalse(local instanceof SlotLocal); + } + } + } + + @Test + public void testSlotIndicesInSameVarNamesInDifferentScopes() { + AnalysisInputLocation inputLocation = + new JavaClassPathAnalysisInputLocation( + directory, SourceType.Application, Collections.emptyList()); + + JavaView view = new JavaView(inputLocation); + JavaSootMethod method = + view.getClass(view.getIdentifierFactory().getClassType("SameVarNamesInDifferentScopes")) + .get() + .getMethod("foo", Collections.emptyList()) + .get(); + + Body body = method.getBody(); + Set locals = body.getLocals(); + + long slotIndexedLocalsCount = locals.stream().filter(SlotLocal.class::isInstance).count(); + assertTrue(slotIndexedLocalsCount > 0, "Expected at least one Local with slot index >= 0"); + + Map localsByName = + locals.stream().collect(Collectors.toMap(Local::getName, l -> l, (a, b) -> a)); + + Local thisLocal = localsByName.get("this"); + assertNotNull(thisLocal); + assertEquals(0, assertInstanceOf(SlotLocal.class, thisLocal).getSlotIndex()); + + Local candidate = localsByName.get("candidate"); + assertNotNull(candidate); + assertEquals(1, assertInstanceOf(SlotLocal.class, candidate).getSlotIndex()); + + Local candidate1 = localsByName.get("candidate_1"); + assertNotNull(candidate1); + assertEquals(2, assertInstanceOf(SlotLocal.class, candidate1).getSlotIndex()); + + Local theresAnother = localsByName.get("theresAnother"); + assertNotNull(theresAnother); + assertEquals(1, assertInstanceOf(SlotLocal.class, theresAnother).getSlotIndex()); + } + + /** + * Verifies slot indices without debug metadata: long and double parameters each occupy two slots, + * and instance methods reserve slot 0 for this. Also checks the next local's slot and that nested + * arithmetic creates {@link StackLocal} temporaries. + */ + @Test + void wideParametersAndLocalsWithoutDebugInformation(@TempDir Path directory) throws Exception { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "WideSlots", null, "java/lang/Object", null); + for (boolean isStatic : List.of(true, false)) { + int base = isStatic ? 0 : 1; + var method = + writer.visitMethod( + Opcodes.ACC_PUBLIC | (isStatic ? Opcodes.ACC_STATIC : 0), + isStatic ? "staticMethod" : "instanceMethod", + "(JDI)I", + null, + null); + method.visitCode(); + method.visitVarInsn(Opcodes.ILOAD, base + 4); + method.visitVarInsn(Opcodes.ILOAD, base + 4); + method.visitInsn(Opcodes.IADD); + // The nested addition requires materializing the first expression into a stack local. + method.visitInsn(Opcodes.ICONST_1); + method.visitInsn(Opcodes.IADD); + method.visitVarInsn(Opcodes.ISTORE, base + 5); + method.visitVarInsn(Opcodes.ILOAD, base + 5); + method.visitInsn(Opcodes.IRETURN); + method.visitMaxs(0, 0); + method.visitEnd(); + } + writer.visitEnd(); + Files.write(directory.resolve("WideSlots.class"), writer.toByteArray()); + var view = + new JavaView( + new JavaClassPathAnalysisInputLocation( + directory.toString(), SourceType.Application, Collections.emptyList())); + var clazz = view.getClass(view.getIdentifierFactory().getClassType("WideSlots")).orElseThrow(); + for (boolean isStatic : List.of(true, false)) { + int base = isStatic ? 0 : 1; + Body body = + clazz + .getMethod( + isStatic ? "staticMethod" : "instanceMethod", + List.of( + PrimitiveType.getLong(), PrimitiveType.getDouble(), PrimitiveType.getInt())) + .orElseThrow() + .getBody(); + var slots = new HashSet(); + for (Local local : body.getLocals()) { + if (local instanceof SlotLocal) { + slots.add(((SlotLocal) local).getSlotIndex()); + } else { + assertInstanceOf(StackLocal.class, local); + } + } + var expected = new HashSet<>(List.of(base, base + 2, base + 4, base + 5)); + if (!isStatic) expected.add(0); + assertEquals(expected, slots); + assertTrue(body.getLocals().stream().anyMatch(StackLocal.class::isInstance)); + for (var stmt : body.getStmts()) { + if (stmt instanceof JIdentityStmt) { + var identity = (JIdentityStmt) stmt; + int slot = assertInstanceOf(SlotLocal.class, identity.getLeftOp()).getSlotIndex(); + if (identity.getRightOp() instanceof JParameterRef) { + int parameter = ((JParameterRef) identity.getRightOp()).getIndex(); + assertEquals(base + 2 * parameter, slot); + } else { + assertInstanceOf(JThisRef.class, identity.getRightOp()); + assertEquals(0, slot); + } + } + } + } + } +} diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/CastAndReturnInlinerTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/CastAndReturnInlinerTest.java index 109169c9a2e..95f1bf5befd 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/CastAndReturnInlinerTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/CastAndReturnInlinerTest.java @@ -6,6 +6,7 @@ import java.util.*; import org.junit.jupiter.api.Test; import sootup.core.graph.MutableControlFlowGraph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.BranchingStmt; @@ -89,8 +90,9 @@ public void testModification() { expected.add(JavaJimple.newReturnStmt(aRet, noPositionInfo)); assertStmtsEquiv(expected, processedBody.getStmts()); assertEquals(2, processedBody.getLocals().size()); - assertTrue(processedBody.getLocals().contains(new Local("a", UnknownType.getInstance()))); - assertTrue(processedBody.getLocals().contains(new Local("a_ret0", UnknownType.getInstance()))); + assertTrue(processedBody.getLocals().contains(Jimple.newLocal("a", UnknownType.getInstance()))); + assertTrue( + processedBody.getLocals().contains(Jimple.newLocal("a_ret0", UnknownType.getInstance()))); } /** diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java index 35860ecc0a8..6310f7f8c91 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java @@ -116,7 +116,7 @@ private static Body.BodyBuilder createBodyBuilder(int constantCondition) { .getMethodSignature( "java.lang.Object", "toString", "String", Collections.emptyList()); Local base = - new Local( + Jimple.newLocal( "someObjectThatHasSomethingToString", factory.getClassType("StringBuilder", "java.lang")); strToB = diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/UnusedLocalEliminatorTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/UnusedLocalEliminatorTest.java index 8bbcc6ee05b..2e418b1cca2 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/UnusedLocalEliminatorTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/UnusedLocalEliminatorTest.java @@ -8,6 +8,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; import sootup.core.graph.MutableControlFlowGraph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.BranchingStmt; @@ -43,8 +44,8 @@ public void testRemoveUnusedDefsAndUses() { assertEquals(4, originalLocals.size()); assertEquals(2, processedLocals.size()); processedLocals = processedBody.getLocals(); - assertTrue(processedLocals.contains(new Local("a", objectType))); - assertTrue(processedLocals.contains(new Local("b", stringType))); + assertTrue(processedLocals.contains(Jimple.newLocal("a", objectType))); + assertTrue(processedLocals.contains(Jimple.newLocal("b", stringType))); } @Test diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/typeresolving/TypeResolverTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/typeresolving/TypeResolverTest.java index 5fa8e8d2414..d2010fda710 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/typeresolving/TypeResolverTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/typeresolving/TypeResolverTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sootup.core.inputlocation.AnalysisInputLocation; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Local; import sootup.core.model.Body; import sootup.core.model.MethodModifier; @@ -182,7 +183,7 @@ public void testArrayMixedAssignment() { // Tests that the augmented integer types (which are based on the value of integer constants) // don't change the type of `a`. - assertLocals(body, new Local("l0", ArrayType.createArrayType(PrimitiveType.getInt(), 1))); + assertLocals(body, Jimple.newLocal("l0", ArrayType.createArrayType(PrimitiveType.getInt(), 1))); } @Test @@ -193,11 +194,11 @@ public void testArrayAssignBeforeInit() { // source code/bytecode), results in the correct type. assertLocals( body, - new Local( + Jimple.newLocal( "l0", ArrayType.createArrayType(identifierFactory.getClassType("String", "java.lang"), 1)), - new Local("$stack1", PrimitiveType.getDouble()), - new Local("$stack2", PrimitiveType.getByte())); + Jimple.newLocal("$stack1", PrimitiveType.getDouble()), + Jimple.newLocal("$stack2", PrimitiveType.getByte())); } @Test @@ -206,7 +207,7 @@ public void testNullArray() { // Tests that assignments to an array index before the array is initialized (in the order of // source code/bytecode), results in the correct type. - assertLocals(body, new Local("l0", ArrayType.createArrayType(objectType, 1))); + assertLocals(body, Jimple.newLocal("l0", ArrayType.createArrayType(objectType, 1))); } @Test @@ -215,7 +216,7 @@ public void testObjectPrimitiveArray() { // Tests that an array that gets both objects and primitives assigned to it, // gets the `TopType[]` type. - assertLocals(body, new Local("l0", ArrayType.createArrayType(TopType.getInstance(), 1))); + assertLocals(body, Jimple.newLocal("l0", ArrayType.createArrayType(TopType.getInstance(), 1))); } @Test @@ -227,9 +228,9 @@ public void testUseNullArray() { // The original paper doesn't do that, and it would only make a difference for this edge case. assertLocals( body, - new Local("l0", objectType), - new Local("#l0", ArrayType.createArrayType(objectType, 1)), - new Local("l1", objectType)); + Jimple.newLocal("l0", objectType), + Jimple.newLocal("#l0", ArrayType.createArrayType(objectType, 1)), + Jimple.newLocal("l1", objectType)); } @Test @@ -240,8 +241,8 @@ public void testUsePrimitiveNullArray() { // array of references. assertLocals( body, - new Local("l0", ArrayType.createArrayType(PrimitiveType.getInt(), 1)), - new Local("l1", PrimitiveType.getInt())); + Jimple.newLocal("l0", ArrayType.createArrayType(PrimitiveType.getInt(), 1)), + Jimple.newLocal("l1", PrimitiveType.getInt())); } @Test @@ -250,8 +251,8 @@ public void testMixedPrimitiveArray() { assertLocals( body, - new Local("l0", PrimitiveType.getInt()), - new Local("l1", ArrayType.createArrayType(PrimitiveType.getByte(), 1))); + Jimple.newLocal("l0", PrimitiveType.getInt()), + Jimple.newLocal("l1", ArrayType.createArrayType(PrimitiveType.getByte(), 1))); } @Test @@ -262,9 +263,9 @@ public void testDependentAugmentedInteger1Promotion() { // But because it gets assigned to `a` which has to be `int`, `b` needs to be an `int` too. assertLocals( body, - new Local("l0", PrimitiveType.getInt()), - new Local("l1", TopType.getInstance()), - new Local("#l0", PrimitiveType.getBoolean())); + Jimple.newLocal("l0", PrimitiveType.getInt()), + Jimple.newLocal("l1", TopType.getInstance()), + Jimple.newLocal("#l0", PrimitiveType.getBoolean())); } @Test @@ -272,7 +273,9 @@ public void testImpossibleTyping() { final Body body = getMiscBody("impossibleTyping"); assertLocals( - body, new Local("l0", TopType.getInstance()), new Local("#l0", PrimitiveType.getBoolean())); + body, + Jimple.newLocal("l0", TopType.getInstance()), + Jimple.newLocal("#l0", PrimitiveType.getBoolean())); } @Test @@ -281,8 +284,8 @@ public void testArrayTest() { assertLocals( body, - new Local("l0", objectType), - new Local("#l0", ArrayType.createArrayType(PrimitiveType.getDouble(), 1))); + Jimple.newLocal("l0", objectType), + Jimple.newLocal("#l0", ArrayType.createArrayType(PrimitiveType.getDouble(), 1))); } @Test @@ -290,9 +293,9 @@ public void testStringDefaultMethodsTest() { final Body body = getMiscBody("testStringDefaultMethodsTest"); assertLocals( body, - new Local("#l0", charSequenceType), - new Local("l0", stringType), - new Local("l1", PrimitiveType.getBoolean())); + Jimple.newLocal("#l0", charSequenceType), + Jimple.newLocal("l0", stringType), + Jimple.newLocal("l1", PrimitiveType.getBoolean())); } @Test @@ -314,10 +317,10 @@ public void testTaAndLnsWithoutLS() { assertLocals( body, - new Local("l0", PrimitiveType.getInt()), - new Local("l1", PrimitiveType.getInt()), - new Local("l2", objectType), - new Local("$stack3", throwableType)); + Jimple.newLocal("l0", PrimitiveType.getInt()), + Jimple.newLocal("l1", PrimitiveType.getInt()), + Jimple.newLocal("l2", objectType), + Jimple.newLocal("$stack3", throwableType)); final MethodSignature methodSignature1 = view.getIdentifierFactory() @@ -326,10 +329,10 @@ public void testTaAndLnsWithoutLS() { assertLocals( body1, - new Local("l0", objectType), - new Local("l1", PrimitiveType.getLong()), - new Local("$stack3", numberType), - new Local("$stack4", throwableType)); + Jimple.newLocal("l0", objectType), + Jimple.newLocal("l1", PrimitiveType.getLong()), + Jimple.newLocal("$stack3", numberType), + Jimple.newLocal("$stack4", throwableType)); final MethodSignature methodSignature2 = view.getIdentifierFactory() @@ -342,12 +345,12 @@ public void testTaAndLnsWithoutLS() { assertLocals( body2, - new Local("this", miscType), - new Local("l1", dateType), - new Local("#l0", illegalArgumentType), - new Local("#l1", throwableType), - new Local("l2", dateType), - new Local("$stack3", objectType)); + Jimple.newLocal("this", miscType), + Jimple.newLocal("l1", dateType), + Jimple.newLocal("#l0", illegalArgumentType), + Jimple.newLocal("#l1", throwableType), + Jimple.newLocal("l2", dateType), + Jimple.newLocal("$stack3", objectType)); } @Test @@ -366,13 +369,13 @@ public void testTaAndLnsWithLS() { assertLocals( body, - new Local("l0#0", PrimitiveType.getByte()), - new Local("l0#2", PrimitiveType.getInt()), - new Local("l2#0", PrimitiveType.getInt()), - new Local("$stack3", sysoutType), - new Local("l0#1", PrimitiveType.getInt()), - new Local("l1", PrimitiveType.getInt()), - new Local("l2#1", throwableType)); + Jimple.newLocal("l0#0", PrimitiveType.getByte()), + Jimple.newLocal("l0#2", PrimitiveType.getInt()), + Jimple.newLocal("l2#0", PrimitiveType.getInt()), + Jimple.newLocal("$stack3", sysoutType), + Jimple.newLocal("l0#1", PrimitiveType.getInt()), + Jimple.newLocal("l1", PrimitiveType.getInt()), + Jimple.newLocal("l2#1", throwableType)); } private void assertLocals(Body body, Local... locals) { diff --git a/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaLocal.java b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaLocal.java index f2551654d67..df4b063b3ec 100644 --- a/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaLocal.java +++ b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaLocal.java @@ -28,42 +28,17 @@ import sootup.java.core.AnnotationUsage; import sootup.java.core.HasAnnotation; -public class JavaLocal extends Local implements HasAnnotation { +/** A local with Java annotations, independent of its slot or operand stack provenance. */ +public interface JavaLocal extends Local, HasAnnotation { + @Override + @NonNull Iterable getAnnotations(); - // TODO: [ms] add to JavaJimple - // TODO: [ms] make use of this class in both Java Frontends + @Override + @NonNull JavaLocal withName(@NonNull String name); - @NonNull private final Iterable annotations; + @Override + @NonNull JavaLocal withType(@NonNull Type type); - /** - * Constructs a JimpleLocal of the given name and type. - * - * @param name - * @param type - */ - public JavaLocal( - @NonNull String name, @NonNull Type type, @NonNull Iterable annotations) { - super(name, type); - this.annotations = annotations; - } - - @NonNull - public Iterable getAnnotations() { - return annotations; - } - - @NonNull - public Local withName(@NonNull String name) { - return new JavaLocal(name, getType(), getAnnotations()); - } - - @NonNull - public Local withType(@NonNull Type type) { - return new JavaLocal(getName(), type, getAnnotations()); - } - - @NonNull - public Local withAnnotations(@NonNull Iterable annotations) { - return new JavaLocal(getName(), getType(), annotations); - } + /** Returns a copy with new annotations, preserving all other state and capabilities. */ + @NonNull JavaLocal withAnnotations(@NonNull Iterable annotations); } diff --git a/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaSlotLocal.java b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaSlotLocal.java new file mode 100644 index 00000000000..840ac408d35 --- /dev/null +++ b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaSlotLocal.java @@ -0,0 +1,48 @@ +package sootup.java.core.jimple.basic; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2020 Markus Schmidt + * %% + * 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 org.jspecify.annotations.NonNull; +import sootup.core.jimple.common.SlotLocal; +import sootup.core.types.Type; +import sootup.java.core.AnnotationUsage; + +/** Combines Java annotations with original JVM local variable slot provenance. */ +public interface JavaSlotLocal extends JavaLocal, SlotLocal { + @Override + @NonNull JavaSlotLocal withName(@NonNull String name); + + @Override + @NonNull JavaSlotLocal withType(@NonNull Type type); + + @Override + @NonNull JavaSlotLocal withAnnotations(@NonNull Iterable annotations); + + @Override + @NonNull JavaSlotLocal withSlotIndex(int slotIndex); + + @Override + default @NonNull JavaSlotLocal withIndex(int slotIndex) { + return withSlotIndex(slotIndex); + } +} diff --git a/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaStackLocal.java b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaStackLocal.java new file mode 100644 index 00000000000..7a3bbe61b07 --- /dev/null +++ b/sootup.java.core/src/main/java/sootup/java/core/jimple/basic/JavaStackLocal.java @@ -0,0 +1,40 @@ +package sootup.java.core.jimple.basic; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2020 Markus Schmidt + * %% + * 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 org.jspecify.annotations.NonNull; +import sootup.core.jimple.common.StackLocal; +import sootup.core.types.Type; +import sootup.java.core.AnnotationUsage; + +/** Combines Java annotations with operand stack provenance. */ +public interface JavaStackLocal extends JavaLocal, StackLocal { + @Override + @NonNull JavaStackLocal withName(@NonNull String name); + + @Override + @NonNull JavaStackLocal withType(@NonNull Type type); + + @Override + @NonNull JavaStackLocal withAnnotations(@NonNull Iterable annotations); +} diff --git a/sootup.java.core/src/main/java/sootup/java/core/language/JavaJimple.java b/sootup.java.core/src/main/java/sootup/java/core/language/JavaJimple.java index e32d1771542..757384e7450 100644 --- a/sootup.java.core/src/main/java/sootup/java/core/language/JavaJimple.java +++ b/sootup.java.core/src/main/java/sootup/java/core/language/JavaJimple.java @@ -27,6 +27,7 @@ import org.jspecify.annotations.NonNull; import sootup.core.IdentifierFactory; import sootup.core.jimple.Jimple; +import sootup.core.jimple.common.AbstractLocal; import sootup.core.jimple.common.constant.ClassConstant; import sootup.core.jimple.common.constant.EnumConstant; import sootup.core.jimple.common.constant.MethodHandle; @@ -41,6 +42,8 @@ import sootup.core.types.VoidType; import sootup.java.core.AnnotationUsage; import sootup.java.core.jimple.basic.JavaLocal; +import sootup.java.core.jimple.basic.JavaSlotLocal; +import sootup.java.core.jimple.basic.JavaStackLocal; /** * JavaJimple implements the Java specific terms for {@link Jimple} @@ -56,7 +59,46 @@ public static boolean isJavaKeywordType(Type t) { /** Constructs a Local with the given name and type. */ public static JavaLocal newLocal(String name, Type t, Iterable annotations) { - return new JavaLocal(name, t, annotations); + return new JavaLocalImpl(name, t, annotations); + } + + /** + * @deprecated Use {@link #newSlotLocal(String, Type, int)} or {@link #newLocal(String, Type)}. + */ + @Deprecated + public static JavaLocal newLocal(String name, Type t, int slotIndex) { + return newLocal(name, t, slotIndex, Collections.emptyList()); + } + + /** + * @deprecated Use an explicit generic or slot local factory. + */ + @Deprecated + public static JavaLocal newLocal( + String name, Type t, int slotIndex, Iterable annotations) { + return slotIndex == -1 + ? newLocal(name, t, annotations) + : newSlotLocal(name, t, slotIndex, annotations); + } + + /** Constructs a temporary originating from the JVM operand stack. */ + public static JavaStackLocal newStackLocal(String name, Type t) { + return newStackLocal(name, t, Collections.emptyList()); + } + + public static JavaStackLocal newStackLocal( + String name, Type t, Iterable annotations) { + return new JavaStackLocalImpl(name, t, annotations); + } + + /** Constructs a local originating from the given nonnegative JVM local variable slot. */ + public static JavaSlotLocal newSlotLocal(String name, Type t, int slotIndex) { + return newSlotLocal(name, t, slotIndex, Collections.emptyList()); + } + + public static JavaSlotLocal newSlotLocal( + String name, Type t, int slotIndex, Iterable annotations) { + return new JavaSlotLocalImpl(name, t, slotIndex, annotations); } /** @@ -108,6 +150,98 @@ public static MethodType newMethodType( /** Constructs a Local with the given name and type. */ public static JavaLocal newLocal(String name, Type t) { - return new JavaLocal(name, t, Collections.emptyList()); + return new JavaLocalImpl(name, t, Collections.emptyList()); + } + + /** Generic Java local with annotations and no JVM slot or operand stack provenance. */ + private static class JavaLocalImpl extends AbstractLocal implements JavaLocal { + @NonNull private final Iterable annotations; + + private JavaLocalImpl(String name, Type type, @NonNull Iterable annotations) { + super(name, type); + this.annotations = annotations; + } + + @Override + public @NonNull Iterable getAnnotations() { + return annotations; + } + + @Override + public @NonNull JavaLocal withName(@NonNull String name) { + return new JavaLocalImpl(name, getType(), annotations); + } + + @Override + public @NonNull JavaLocal withType(@NonNull Type type) { + return new JavaLocalImpl(getName(), type, annotations); + } + + @Override + public @NonNull JavaLocal withAnnotations(@NonNull Iterable annotations) { + return new JavaLocalImpl(getName(), getType(), annotations); + } + } + + /** Operand stack temporary that preserves Java annotations when copied. */ + private static final class JavaStackLocalImpl extends JavaLocalImpl implements JavaStackLocal { + private JavaStackLocalImpl( + String name, Type type, @NonNull Iterable annotations) { + super(name, type, annotations); + } + + @Override + public @NonNull JavaStackLocal withName(@NonNull String name) { + return new JavaStackLocalImpl(name, getType(), getAnnotations()); + } + + @Override + public @NonNull JavaStackLocal withType(@NonNull Type type) { + return new JavaStackLocalImpl(getName(), type, getAnnotations()); + } + + @Override + public @NonNull JavaStackLocal withAnnotations(@NonNull Iterable annotations) { + return new JavaStackLocalImpl(getName(), getType(), annotations); + } + } + + /** Local preserving its original JVM slot index and Java annotations when copied. */ + private static final class JavaSlotLocalImpl extends JavaLocalImpl implements JavaSlotLocal { + private final int slotIndex; + + private JavaSlotLocalImpl( + String name, Type type, int slotIndex, @NonNull Iterable annotations) { + super(name, type, annotations); + if (slotIndex < 0) { + throw new IllegalArgumentException("Slot index must be nonnegative"); + } + this.slotIndex = slotIndex; + } + + @Override + public int getSlotIndex() { + return slotIndex; + } + + @Override + public @NonNull JavaSlotLocal withName(@NonNull String name) { + return new JavaSlotLocalImpl(name, getType(), slotIndex, getAnnotations()); + } + + @Override + public @NonNull JavaSlotLocal withType(@NonNull Type type) { + return new JavaSlotLocalImpl(getName(), type, slotIndex, getAnnotations()); + } + + @Override + public @NonNull JavaSlotLocal withAnnotations(@NonNull Iterable annotations) { + return new JavaSlotLocalImpl(getName(), getType(), slotIndex, annotations); + } + + @Override + public @NonNull JavaSlotLocal withSlotIndex(int slotIndex) { + return new JavaSlotLocalImpl(getName(), getType(), slotIndex, getAnnotations()); + } } } diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/LocalTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/LocalTest.java index 4353cecd8f9..d7b8145bf68 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/LocalTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/LocalTest.java @@ -24,13 +24,24 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.IgnoreLocalNameComparator; import sootup.core.jimple.basic.JimpleComparator; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; +import sootup.core.jimple.common.SlotLocal; +import sootup.core.jimple.common.StackLocal; import sootup.core.jimple.javabytecode.stmt.JBreakpointStmt; import sootup.core.types.PrimitiveType; +import sootup.java.core.AnnotationUsage; +import sootup.java.core.JavaIdentifierFactory; +import sootup.java.core.jimple.basic.JavaLocal; +import sootup.java.core.jimple.basic.JavaSlotLocal; +import sootup.java.core.jimple.basic.JavaStackLocal; +import sootup.java.core.language.JavaJimple; public class LocalTest { @@ -39,9 +50,9 @@ public void testEquivTo() { JimpleComparator comparator = new IgnoreLocalNameComparator(); - Local l1 = new Local("i1", PrimitiveType.getInt()); - Local l2 = new Local("i2", PrimitiveType.getInt()); - Local l3 = new Local("i1", PrimitiveType.getBoolean()); + Local l1 = Jimple.newLocal("i1", PrimitiveType.getInt()); + Local l2 = Jimple.newLocal("i2", PrimitiveType.getInt()); + Local l3 = Jimple.newLocal("i1", PrimitiveType.getBoolean()); assertTrue(l1.equivTo(l1)); assertTrue(l1.equivTo(l1, comparator)); @@ -58,4 +69,115 @@ public void testEquivTo() { assertFalse( l1.equivTo(new JBreakpointStmt(StmtPositionInfo.getNoStmtPositionInfo()), comparator)); } + + @Test + public void javaWithersPreserveProvenanceAndAnnotations() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + List locals = + List.of( + JavaJimple.newLocal("generic", PrimitiveType.getInt(), annotations), + JavaJimple.newStackLocal("stack", PrimitiveType.getInt(), annotations), + JavaJimple.newSlotLocal("slot", PrimitiveType.getInt(), 7, annotations)); + for (JavaLocal local : locals) { + for (JavaLocal copy : + List.of(local.withName("renamed"), local.withType(PrimitiveType.getFloat()))) { + assertSame(local.getClass(), copy.getClass()); + assertSame(annotations, copy.getAnnotations()); + if (local instanceof SlotLocal) { + assertEquals(7, ((SlotLocal) copy).getSlotIndex()); + } + } + JavaLocal copy = local.withAnnotations(List.of()); + assertSame(local.getClass(), copy.getClass()); + assertFalse(copy.getAnnotations().iterator().hasNext()); + if (copy instanceof SlotLocal) { + assertEquals(7, ((SlotLocal) copy).getSlotIndex()); + } + } + var slot = JavaJimple.newSlotLocal("slot", PrimitiveType.getInt(), 7, annotations); + assertEquals(8, slot.withSlotIndex(8).getSlotIndex()); + assertSame(annotations, slot.withSlotIndex(8).getAnnotations()); + assertEquals(Jimple.newLocal("slot", PrimitiveType.getInt()), slot); + assertTrue(Jimple.newStackLocal("slot", PrimitiveType.getInt()).equivTo(slot)); + } + + @Test + @SuppressWarnings("deprecation") + public void javaFactoriesValidateSlots() { + assertInstanceOf(SlotLocal.class, JavaJimple.newLocal("a", PrimitiveType.getInt(), 0)); + JavaLocal generic = JavaJimple.newLocal("a", PrimitiveType.getInt(), -1); + assertFalse(generic instanceof SlotLocal); + assertFalse(generic instanceof StackLocal); + assertThrows( + IllegalArgumentException.class, () -> JavaJimple.newLocal("a", PrimitiveType.getInt(), -2)); + assertThrows( + IllegalArgumentException.class, + () -> JavaJimple.newSlotLocal("a", PrimitiveType.getInt(), -1)); + assertThrows( + IllegalArgumentException.class, + () -> JavaJimple.newSlotLocal("a", PrimitiveType.getInt(), 0).withSlotIndex(-1)); + } + + @Test + public void combinedInterfacesSupportFluentCopies() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + JavaSlotLocal slot = JavaJimple.newSlotLocal("slot", PrimitiveType.getInt(), 2); + JavaSlotLocal copy = + slot.withAnnotations(annotations) + .withSlotIndex(4) + .withName("renamed") + .withType(PrimitiveType.getFloat()) + .withIndex(6); + assertEquals(6, copy.getSlotIndex()); + assertEquals("renamed", copy.getName()); + assertEquals(PrimitiveType.getFloat(), copy.getType()); + assertSame(annotations, copy.getAnnotations()); + assertEquals(2, slot.getSlotIndex()); + assertFalse(slot.getAnnotations().iterator().hasNext()); + + JavaStackLocal stack = JavaJimple.newStackLocal("stack", PrimitiveType.getInt()); + JavaStackLocal stackCopy = + stack.withAnnotations(annotations).withName("renamed").withType(PrimitiveType.getFloat()); + assertSame(annotations, stackCopy.getAnnotations()); + assertInstanceOf(StackLocal.class, stackCopy); + assertFalse(stackCopy instanceof SlotLocal); + } + + @Test + public void copiesThroughEachInterfacePreserveOtherCapabilities() { + var annotations = + List.of( + new AnnotationUsage( + new JavaIdentifierFactory().getClassType("example.Annotation"), Map.of())); + JavaSlotLocal original = + JavaJimple.newSlotLocal("slot", PrimitiveType.getInt(), 7, annotations); + Local localView = original; + SlotLocal slotView = original; + JavaLocal javaView = original; + for (Local copy : + List.of( + localView.withName("fromLocal"), + localView.withType(PrimitiveType.getFloat()), + slotView.withName("fromSlot"), + slotView.withType(PrimitiveType.getFloat()), + javaView.withName("fromJava"), + javaView.withType(PrimitiveType.getFloat()))) { + JavaSlotLocal combined = assertInstanceOf(JavaSlotLocal.class, copy); + assertEquals(7, combined.getSlotIndex()); + assertSame(annotations, combined.getAnnotations()); + } + var changedSlot = assertInstanceOf(JavaSlotLocal.class, slotView.withSlotIndex(8)); + assertEquals(8, changedSlot.getSlotIndex()); + assertSame(annotations, changedSlot.getAnnotations()); + var changedAnnotations = + assertInstanceOf(JavaSlotLocal.class, javaView.withAnnotations(List.of())); + assertEquals(7, changedAnnotations.getSlotIndex()); + assertFalse(changedAnnotations.getAnnotations().iterator().hasNext()); + } } diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/ref/JFieldRefTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/ref/JFieldRefTest.java index d6542a12be1..fd27f282dc0 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/ref/JFieldRefTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/ref/JFieldRefTest.java @@ -101,7 +101,7 @@ public void testJInstanceFieldRef() { Collections.emptyList(), Collections.emptyList()), SourceType.Application); - Local base = new Local("obj", declaringClassSignature); + Local base = Jimple.newLocal("obj", declaringClassSignature); JInstanceFieldRef ref = Jimple.newInstanceFieldRef(base, fieldSig); assertEquals("obj.", ref.toString()); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JAssignStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JAssignStmtTest.java index 5e70af90c1d..fddd4a2b5f9 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JAssignStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JAssignStmtTest.java @@ -67,8 +67,8 @@ public void test() { Immediate numConst1 = IntConstant.getInstance(42); Immediate numConst2 = IntConstant.getInstance(33102); - Local local = new Local("i0", PrimitiveType.getInt()); - Local field = new Local("i2", PrimitiveType.getInt()); + Local local = Jimple.newLocal("i0", PrimitiveType.getInt()); + Local field = Jimple.newLocal("i2", PrimitiveType.getInt()); Stmt lStmt = new JAssignStmt(local, numConst1, nop); Stmt fStmt = new JAssignStmt(field, numConst1, nop); @@ -79,12 +79,14 @@ public void test() { assertTrue( lStmt.equivTo( new JAssignStmt( - new Local("i0", PrimitiveType.getInt()), IntConstant.getInstance(42), nop))); + Jimple.newLocal("i0", PrimitiveType.getInt()), IntConstant.getInstance(42), nop))); assertTrue( deepStmt.equivTo( new JAssignStmt( - new Local("i0", PrimitiveType.getInt()), new JAddExpr(numConst1, numConst2), nop))); + Jimple.newLocal("i0", PrimitiveType.getInt()), + new JAddExpr(numConst1, numConst2), + nop))); // equivTo: switched operands assertFalse(lStmt.equivTo(new JAssignStmt(local, numConst2, nop))); @@ -95,13 +97,15 @@ public void test() { assertFalse( lStmt.equivTo( new JAssignStmt( - new Local("i100differentname", PrimitiveType.getInt()), + Jimple.newLocal("i100differentname", PrimitiveType.getInt()), IntConstant.getInstance(42), nop))); assertFalse( lStmt.equivTo( new JAssignStmt( - new Local("i0", PrimitiveType.getLong()), LongConstant.getInstance(42), nop))); + Jimple.newLocal("i0", PrimitiveType.getLong()), + LongConstant.getInstance(42), + nop))); // equivTo: different depth assertFalse(lStmt.equivTo(new JAssignStmt(field, new JAddExpr(numConst1, numConst2), nop))); @@ -116,7 +120,7 @@ public void test() { // test JFieldRef cast for JFieldRef - should not throw an Exception Local someLocal = - new Local("r42", new JavaIdentifierFactory().getClassType("Abc.def.Alphabet")); + Jimple.newLocal("r42", new JavaIdentifierFactory().getClassType("Abc.def.Alphabet")); final JStaticFieldRef somefield = Jimple.newStaticFieldRef( new JavaIdentifierFactory() diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JGotoStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JGotoStmtTest.java index 82369397202..7916d59d559 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JGotoStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JGotoStmtTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.JGotoStmt; @@ -44,7 +45,7 @@ public void test() { StmtPositionInfo nop = StmtPositionInfo.getNoStmtPositionInfo(); JavaIdentifierFactory typeFactory = new JavaIdentifierFactory(); - Local local = new Local("r0", typeFactory.getType("java.lang.Exception")); + Local local = Jimple.newLocal("r0", typeFactory.getType("java.lang.Exception")); // Stmt Stmt targetStmt = new JThrowStmt(local, nop); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JIdentityStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JIdentityStmtTest.java index 21125a45d2f..8a25416d726 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JIdentityStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JIdentityStmtTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.ref.JParameterRef; @@ -45,16 +46,16 @@ public void test() { StmtPositionInfo nop = StmtPositionInfo.getNoStmtPositionInfo(); JavaIdentifierFactory typeFactory = new JavaIdentifierFactory(); - Local thiz = new Local("r0", typeFactory.getType("somepackage.dummy.MyClass")); + Local thiz = Jimple.newLocal("r0", typeFactory.getType("somepackage.dummy.MyClass")); Stmt thisIdStmt = new JIdentityStmt( thiz, new JThisRef(typeFactory.getClassType("somepackage.dummy.MyClass")), nop); - Local param = new Local("i0", PrimitiveType.getInt()); + Local param = Jimple.newLocal("i0", PrimitiveType.getInt()); Stmt paramIdStmt = new JIdentityStmt(param, new JParameterRef(PrimitiveType.getInt(), 123), nop); - Local exception = new Local("r1", typeFactory.getType("java.lang.Exception")); + Local exception = Jimple.newLocal("r1", typeFactory.getType("java.lang.Exception")); Stmt exceptionIdStmt = new JIdentityStmt(exception, JavaJimple.newCaughtExceptionRef(typeFactory), nop); @@ -67,13 +68,13 @@ public void test() { assertFalse( thisIdStmt.equivTo( new JIdentityStmt( - new Local("r5", typeFactory.getType("somepackage.NotMyClass")), + Jimple.newLocal("r5", typeFactory.getType("somepackage.NotMyClass")), new JThisRef(typeFactory.getClassType("somepackage.NotMyClass")), nop))); assertFalse( thisIdStmt.equivTo( new JIdentityStmt( - new Local("r42", typeFactory.getType("somepackage.dummy.MyClass")), + Jimple.newLocal("r42", typeFactory.getType("somepackage.dummy.MyClass")), new JThisRef(typeFactory.getClassType("somepackage.dummy.MyClass")), nop))); assertTrue(thisIdStmt.equivTo(thisIdStmt)); @@ -83,13 +84,13 @@ public void test() { assertFalse( thisIdStmt.equivTo( new JIdentityStmt( - new Local("i1", PrimitiveType.getInt()), + Jimple.newLocal("i1", PrimitiveType.getInt()), new JParameterRef(PrimitiveType.getInt(), 123), nop))); assertFalse( thisIdStmt.equivTo( new JIdentityStmt( - new Local("i0", PrimitiveType.getInt()), + Jimple.newLocal("i0", PrimitiveType.getInt()), new JParameterRef(PrimitiveType.getInt(), 42), nop))); assertFalse(exceptionIdStmt.equivTo(thisIdStmt)); @@ -99,7 +100,7 @@ public void test() { assertFalse( thisIdStmt.equivTo( new JIdentityStmt( - new Local("r1", typeFactory.getType("somepckg.NotMyException")), + Jimple.newLocal("r1", typeFactory.getType("somepckg.NotMyException")), JavaJimple.newCaughtExceptionRef(typeFactory), nop))); assertFalse(paramIdStmt.equivTo(thisIdStmt)); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JInvokeStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JInvokeStmtTest.java index 19f8af1a2ac..51b9a77877f 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JInvokeStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JInvokeStmtTest.java @@ -35,10 +35,10 @@ import java.util.Set; import org.junit.jupiter.api.Test; import sootup.core.inputlocation.EagerInputLocation; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.NoPositionInformation; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Immediate; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.expr.JDynamicInvokeExpr; import sootup.core.jimple.common.expr.JInterfaceInvokeExpr; import sootup.core.jimple.common.expr.JSpecialInvokeExpr; @@ -113,7 +113,7 @@ public void test() { Stmt specialInvokeStmt = new JInvokeStmt( new JSpecialInvokeExpr( - new Local("r0", sootClass.getType()), smethodSig, Collections.emptyList()), + Jimple.newLocal("r0", sootClass.getType()), smethodSig, Collections.emptyList()), nop); // toString @@ -130,7 +130,7 @@ public void test() { Stmt interfaceInvokeStmt = new JInvokeStmt( new JInterfaceInvokeExpr( - new Local("r2", sootClass.getType()), imethodSig, Collections.emptyList()), + Jimple.newLocal("r2", sootClass.getType()), imethodSig, Collections.emptyList()), nop); // toString diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JNopStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JNopStmtTest.java index f901a921f61..bc1a6095269 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JNopStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JNopStmtTest.java @@ -25,8 +25,8 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.ref.JParameterRef; import sootup.core.jimple.common.stmt.JIdentityStmt; import sootup.core.jimple.common.stmt.JNopStmt; @@ -49,7 +49,7 @@ public void test() { assertFalse( nop.equivTo( new JIdentityStmt( - new Local("i0", PrimitiveType.getInt()), + Jimple.newLocal("i0", PrimitiveType.getInt()), new JParameterRef(PrimitiveType.getInt(), 123), nopos))); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JThrowStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JThrowStmtTest.java index 6e9f0430384..4fde48d285a 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JThrowStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/common/stmt/JThrowStmtTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.JNopStmt; @@ -42,10 +43,10 @@ public void test() { StmtPositionInfo nop = StmtPositionInfo.getNoStmtPositionInfo(); JavaIdentifierFactory typeFactory = new JavaIdentifierFactory(); - Local local = new Local("r0", typeFactory.getType("java.lang.Exception")); - Local localEqual = new Local("r0", typeFactory.getType("java.lang.Exception")); - Local localDifferent = new Local("r1", typeFactory.getType("java.lang.Exception")); - Local localDifferent2 = new Local("r0", typeFactory.getType("sompepackage.MyException")); + Local local = Jimple.newLocal("r0", typeFactory.getType("java.lang.Exception")); + Local localEqual = Jimple.newLocal("r0", typeFactory.getType("java.lang.Exception")); + Local localDifferent = Jimple.newLocal("r1", typeFactory.getType("java.lang.Exception")); + Local localDifferent2 = Jimple.newLocal("r0", typeFactory.getType("sompepackage.MyException")); Stmt tStmt = new JThrowStmt(local, nop); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JEnterMonitorStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JEnterMonitorStmtTest.java index 051090f4523..5cfb91dd484 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JEnterMonitorStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JEnterMonitorStmtTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.Stmt; @@ -39,9 +40,9 @@ public class JEnterMonitorStmtTest { @Test public void test() { StmtPositionInfo nop = StmtPositionInfo.getNoStmtPositionInfo(); - Local sandman = new Local("sandman", PrimitiveType.getInt()); - Local night = new Local("night", PrimitiveType.getBoolean()); - Local light = new Local("light", PrimitiveType.getBoolean()); + Local sandman = Jimple.newLocal("sandman", PrimitiveType.getInt()); + Local night = Jimple.newLocal("night", PrimitiveType.getBoolean()); + Local light = Jimple.newLocal("light", PrimitiveType.getBoolean()); Stmt stmt = new JEnterMonitorStmt(sandman, nop); Stmt nightStmt = new JEnterMonitorStmt(night, nop); diff --git a/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JExitMonitorStmtTest.java b/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JExitMonitorStmtTest.java index 0e010b31e1c..743d5288460 100644 --- a/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JExitMonitorStmtTest.java +++ b/sootup.java.core/src/test/java/sootup/java/core/jimple/javabytecode/stmt/JExitMonitorStmtTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.Stmt; @@ -39,9 +40,9 @@ public class JExitMonitorStmtTest { @Test public void test() { StmtPositionInfo nop = StmtPositionInfo.getNoStmtPositionInfo(); - Local sandman = new Local("sandman", PrimitiveType.getInt()); - Local night = new Local("night", PrimitiveType.getBoolean()); - Local light = new Local("light", PrimitiveType.getBoolean()); + Local sandman = Jimple.newLocal("sandman", PrimitiveType.getInt()); + Local night = Jimple.newLocal("night", PrimitiveType.getBoolean()); + Local light = Jimple.newLocal("light", PrimitiveType.getBoolean()); Stmt stmt = new JExitMonitorStmt(sandman, nop); Stmt nightStmt = new JExitMonitorStmt(night, nop); diff --git a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/JimpleBodyConverterState.java b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/JimpleBodyConverterState.java index 76908513023..3ca8665f0fc 100644 --- a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/JimpleBodyConverterState.java +++ b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/JimpleBodyConverterState.java @@ -27,6 +27,7 @@ import java.util.Map; import org.jspecify.annotations.NonNull; import sootup.core.IdentifierFactory; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.stmt.BranchingStmt; import sootup.core.types.ClassType; @@ -87,6 +88,7 @@ public Map getLocals() { @NonNull public Local getLocal(@NonNull String name) { - return locals.computeIfAbsent(name, (ignored) -> new Local(name, UnknownType.getInstance())); + return locals.computeIfAbsent( + name, (ignored) -> Jimple.newLocal(name, UnknownType.getInstance())); } } diff --git a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/LazyJimpleMethodSource.java b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/LazyJimpleMethodSource.java index b5c4368b73a..adef8cbbbc9 100644 --- a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/LazyJimpleMethodSource.java +++ b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/LazyJimpleMethodSource.java @@ -165,7 +165,7 @@ public Body parseMethodBody() { for (JimpleParser.ImmediateContext immediate : immediates) { if (immediate != null && immediate.local != null) { String localname = immediate.local.getText(); - locals.put(localname, new Local(localname, localtype)); + locals.put(localname, Jimple.newLocal(localname, localtype)); } else { throw new ResolveException( "Thats not a Local in the Local Declaration.", diff --git a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/MethodVisitor.java b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/MethodVisitor.java index fb0b380ed4f..f5b0690c377 100644 --- a/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/MethodVisitor.java +++ b/sootup.jimple.frontend/src/main/java/sootup/jimple/frontend/MethodVisitor.java @@ -147,7 +147,7 @@ protected void parseMethodBody( for (JimpleParser.ImmediateContext immediate : immediates) { if (immediate != null && immediate.local != null) { String localname = immediate.local.getText(); - locals.put(localname, new Local(localname, localtype)); + locals.put(localname, Jimple.newLocal(localname, localtype)); } else { throw new ResolveException( "Thats not a Local in the Local Declaration.", diff --git a/sootup.spark/src/test/java/sootup/spark/test/PointsToAnalysisTest.java b/sootup.spark/src/test/java/sootup/spark/test/PointsToAnalysisTest.java index 82c37d6ca48..27b6ee6f6fb 100644 --- a/sootup.spark/src/test/java/sootup/spark/test/PointsToAnalysisTest.java +++ b/sootup.spark/src/test/java/sootup/spark/test/PointsToAnalysisTest.java @@ -10,6 +10,7 @@ import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.ref.JInstanceFieldRef; import sootup.core.signatures.FieldSignature; @@ -47,25 +48,26 @@ public void reachingObjectsAndTypesBasic() { assertEquals( Collections.singleton(newField), - pta.reachingObjects(new Local("$stack5", fieldType), mainSig)); + pta.reachingObjects(Jimple.newLocal("$stack5", fieldType), mainSig)); assertEquals( - Collections.singleton(newField), pta.reachingObjects(new Local("l2", fieldType), mainSig)); + Collections.singleton(newField), + pta.reachingObjects(Jimple.newLocal("l2", fieldType), mainSig)); assertEquals( Collections.singleton(newContainer), - pta.reachingObjects(new Local("$stack6", containerType), mainSig)); + pta.reachingObjects(Jimple.newLocal("$stack6", containerType), mainSig)); assertEquals( Collections.singleton(newContainer), - pta.reachingObjects(new Local("l3", containerType), mainSig)); + pta.reachingObjects(Jimple.newLocal("l3", containerType), mainSig)); assertEquals( Collections.singleton(newContainer), - pta.reachingObjects(new Local("l4", containerType), mainSig)); + pta.reachingObjects(Jimple.newLocal("l4", containerType), mainSig)); assertEquals( Collections.singleton((Type) fieldType), - pta.reachingTypes(new Local("l2", fieldType), mainSig)); + pta.reachingTypes(Jimple.newLocal("l2", fieldType), mainSig)); assertEquals( Collections.singleton((Type) containerType), - pta.reachingTypes(new Local("l3", containerType), mainSig)); + pta.reachingTypes(Jimple.newLocal("l3", containerType), mainSig)); } @Test @@ -80,8 +82,8 @@ public void aliasesBasic() { ClassType fieldType = SparkTestUtil.idFactory.getClassType("Basic$Field"); ClassType containerType = SparkTestUtil.idFactory.getClassType("Basic$Container"); - Local l2 = new Local("l2", fieldType); - Local l3 = new Local("l3", containerType); + Local l2 = Jimple.newLocal("l2", fieldType); + Local l3 = Jimple.newLocal("l3", containerType); Node stack5Node = SparkTestUtil.var(fieldType, "$stack5", mainSig); Node l2Node = SparkTestUtil.var(fieldType, "l2", mainSig); @@ -117,7 +119,7 @@ public void instanceFieldRefReachingObjects() { FieldSignature fieldSig = SparkTestUtil.idFactory.getFieldSignature("field", containerType, fieldType); JInstanceFieldRef stack6FieldRef = - new JInstanceFieldRef(new Local("$stack6", containerType), fieldSig); + new JInstanceFieldRef(Jimple.newLocal("$stack6", containerType), fieldSig); AllocationNode newField = SparkTestUtil.alloc(fieldType, 1L, mainSig); assertEquals(Collections.singleton(newField), pta.reachingObjects(stack6FieldRef, mainSig)); @@ -138,9 +140,9 @@ public void interproceduralLoadAndReturn() { AllocationNode newO1 = SparkTestUtil.alloc(oType, 1L, mainSig); AllocationNode newO2 = SparkTestUtil.alloc(oType, 2L, mainSig); - Local p = new Local("l1", oType); - Local r = new Local("l3", oType); - Local t = new Local("l4", oType); + Local p = Jimple.newLocal("l1", oType); + Local r = Jimple.newLocal("l3", oType); + Local t = Jimple.newLocal("l4", oType); Node rNode = SparkTestUtil.var(oType, "l3", mainSig); @@ -175,9 +177,11 @@ public void voidCallInterParamReachingObjects() { // dst(l0) receives t (alloc3); src(l1) receives q which aliases p (alloc1) assertEquals( - Collections.singleton(newO3), pta.reachingObjects(new Local("l0", oType), copyValueSig)); + Collections.singleton(newO3), + pta.reachingObjects(Jimple.newLocal("l0", oType), copyValueSig)); assertEquals( - Collections.singleton(newO1), pta.reachingObjects(new Local("l1", oType), copyValueSig)); + Collections.singleton(newO1), + pta.reachingObjects(Jimple.newLocal("l1", oType), copyValueSig)); } /** @@ -201,10 +205,10 @@ public void voidCallInterCrossMethodAliases() { // q (passed as src) aliases copyValue:src; t (passed as dst) aliases copyValue:dst assertTrue( - pta.aliases(new Local("l2", oType), mainSig).contains(srcNode), + pta.aliases(Jimple.newLocal("l2", oType), mainSig).contains(srcNode), "q aliases copyValue:src across call boundary"); assertTrue( - pta.aliases(new Local("l4", oType), mainSig).contains(dstNode), + pta.aliases(Jimple.newLocal("l4", oType), mainSig).contains(dstNode), "t aliases copyValue:dst across call boundary"); } @@ -228,7 +232,7 @@ public void voidCallInterHeapPropagationAfterCopy() { // t.f must reach alloc2 via the inter-proc store chain: dst.f = src.f where src aliases p assertEquals( Collections.singleton(newO2), - pta.reachingObjects(new JInstanceFieldRef(new Local("l4", oType), fSig), mainSig)); + pta.reachingObjects(new JInstanceFieldRef(Jimple.newLocal("l4", oType), fSig), mainSig)); } /** @@ -254,7 +258,7 @@ public void selfReferentialFieldLoadDoesNotThrowConcurrentModificationException( // x merges {a, b} from the branch, then "x = x.f" must pull in c (stored via a.f = c and // b.f = c) without dropping the pre-existing allocations. - Set reachingX = pta.reachingObjects(new Local("l4", nodeType), mainSig); + Set reachingX = pta.reachingObjects(Jimple.newLocal("l4", nodeType), mainSig); assertTrue(reachingX.containsAll(Set.of(newA, newB, newC)), reachingX::toString); } } diff --git a/sootup.spark/src/test/java/sootup/spark/test/StmtToEdgeConversionTest.java b/sootup.spark/src/test/java/sootup/spark/test/StmtToEdgeConversionTest.java index e3c54bb6b16..2a249aa6a91 100644 --- a/sootup.spark/src/test/java/sootup/spark/test/StmtToEdgeConversionTest.java +++ b/sootup.spark/src/test/java/sootup/spark/test/StmtToEdgeConversionTest.java @@ -9,9 +9,9 @@ import org.graph4j.Edge; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.StmtPositionInfo; import sootup.core.jimple.common.LValue; -import sootup.core.jimple.common.Local; import sootup.core.jimple.common.Value; import sootup.core.jimple.common.expr.JNewExpr; import sootup.core.jimple.common.ref.JInstanceFieldRef; @@ -45,7 +45,7 @@ public void reset() { @Test public void testAllocEdge() { - LValue left = new Local("a", aType); + LValue left = Jimple.newLocal("a", aType); Value right = new JNewExpr(SparkTestUtil.simpleType("A")); val methodPAG = new PAG(SparkOptions.defaultOptions()); val edge = doAssignment(right, left, methodPAG, PAGEdge.EdgeType.ALLOCATION); @@ -58,8 +58,8 @@ public void testAllocEdge() { @Test public void testAssignEdge() { - LValue left = new Local("b", aType); - Value right = new Local("a", aType); + LValue left = Jimple.newLocal("b", aType); + Value right = Jimple.newLocal("a", aType); val methodPAG = new PAG(SparkOptions.defaultOptions()); val edge = doAssignment(right, left, methodPAG, PAGEdge.EdgeType.ASSIGNMENT); @@ -71,9 +71,9 @@ public void testAssignEdge() { @Test public void testStoreEdge() { - val base = new Local("someB", aType); + val base = Jimple.newLocal("someB", aType); val right = new JInstanceFieldRef(base, fieldSig); - LValue left = new Local("b", aType); + LValue left = Jimple.newLocal("b", aType); val methodPAG = new PAG(SparkOptions.defaultOptions()); val edge = doAssignment(right, left, methodPAG, PAGEdge.EdgeType.LOAD); @@ -92,9 +92,9 @@ public void testStoreEdge() { @Test public void testLoadEdge() { - val base = new Local("someB", aType); + val base = Jimple.newLocal("someB", aType); val left = new JInstanceFieldRef(base, fieldSig); - LValue right = new Local("b", aType); + LValue right = Jimple.newLocal("b", aType); val methodPAG = new PAG(SparkOptions.defaultOptions()); val edge = doAssignment(right, left, methodPAG, PAGEdge.EdgeType.STORE); diff --git a/sootup.spark/src/test/java/sootup/spark/test/ValueToNodeConversionTest.java b/sootup.spark/src/test/java/sootup/spark/test/ValueToNodeConversionTest.java index 64c15f6be6a..907222f0cf9 100644 --- a/sootup.spark/src/test/java/sootup/spark/test/ValueToNodeConversionTest.java +++ b/sootup.spark/src/test/java/sootup/spark/test/ValueToNodeConversionTest.java @@ -8,6 +8,7 @@ import lombok.val; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.Local; import sootup.core.jimple.common.constant.BooleanConstant; import sootup.core.jimple.common.constant.DoubleConstant; @@ -54,7 +55,7 @@ public void reset() { @Test void testLocalToNodeConversion() { // Local variable - val local = new Local("a", aType); + val local = Jimple.newLocal("a", aType); val varNodeOpt = nodeFactory.createNode(local, methodSig); assertTrue(varNodeOpt.isPresent()); val varNode = varNodeOpt.get(); @@ -80,7 +81,7 @@ void testNewExprToNodeConversion() { @Test void testInstanceFieldRefToNodeConversion() { // Instance FieldRef class A{B someB.f} - val base = new Local("someB", aType); + val base = Jimple.newLocal("someB", aType); val instanceFieldRef = new JInstanceFieldRef(base, fieldSig); val instanceFieldRefNodeOpt = nodeFactory.createNode(instanceFieldRef, methodSig); assertTrue(instanceFieldRefNodeOpt.isPresent()); @@ -112,7 +113,7 @@ void testStaticFieldRefToNodeConversion() { void testArrayElementToNodeConversion() { // Array Element val arrayType = ArrayType.createArrayType(aType, 1); - val arrayRef = new JArrayRef(new Local("array", arrayType), IntConstant.getInstance(42)); + val arrayRef = new JArrayRef(Jimple.newLocal("array", arrayType), IntConstant.getInstance(42)); val arrayRefNodeOpt = nodeFactory.createNode(arrayRef, methodSig); assertTrue(arrayRefNodeOpt.isPresent()); val arrayNode = arrayRefNodeOpt.get(); @@ -220,8 +221,8 @@ void testNullConstantToNodeConversion() { @Test void testAddExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val addExpr = JavaJimple.newAddExpr(local1, local2); val node = nodeFactory.createNode(addExpr, methodSig); assertTrue(node.isEmpty()); @@ -229,8 +230,8 @@ void testAddExprToNodeConversion() { @Test void testAndExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val andExpr = JavaJimple.newAndExpr(local1, local2); val node = nodeFactory.createNode(andExpr, methodSig); assertTrue(node.isEmpty()); @@ -238,8 +239,8 @@ void testAndExprToNodeConversion() { @Test void testCmpExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val cmpExpr = JavaJimple.newCmpExpr(local1, local2); val node = nodeFactory.createNode(cmpExpr, methodSig); assertTrue(node.isEmpty()); @@ -247,8 +248,8 @@ void testCmpExprToNodeConversion() { @Test void testCmpgExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val cmpgExpr = JavaJimple.newCmpgExpr(local1, local2); val node = nodeFactory.createNode(cmpgExpr, methodSig); assertTrue(node.isEmpty()); @@ -256,8 +257,8 @@ void testCmpgExprToNodeConversion() { @Test void testCmplExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val cmplExpr = JavaJimple.newCmplExpr(local1, local2); val node = nodeFactory.createNode(cmplExpr, methodSig); assertTrue(node.isEmpty()); @@ -265,8 +266,8 @@ void testCmplExprToNodeConversion() { @Test void testDivExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val divExpr = JavaJimple.newDivExpr(local1, local2); val node = nodeFactory.createNode(divExpr, methodSig); assertTrue(node.isEmpty()); @@ -274,8 +275,8 @@ void testDivExprToNodeConversion() { @Test void testEqExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val eqExpr = JavaJimple.newEqExpr(local1, local2); val node = nodeFactory.createNode(eqExpr, methodSig); assertTrue(node.isEmpty()); @@ -283,8 +284,8 @@ void testEqExprToNodeConversion() { @Test void testGeExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val geExpr = JavaJimple.newGeExpr(local1, local2); val node = nodeFactory.createNode(geExpr, methodSig); assertTrue(node.isEmpty()); @@ -292,8 +293,8 @@ void testGeExprToNodeConversion() { @Test void testGtExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val gtExpr = JavaJimple.newGtExpr(local1, local2); val node = nodeFactory.createNode(gtExpr, methodSig); assertTrue(node.isEmpty()); @@ -301,7 +302,7 @@ void testGtExprToNodeConversion() { @Test void testInstanceOfExprToNodeConversion() { - val local = new Local("a", aType); + val local = Jimple.newLocal("a", aType); val instanceOfExpr = JavaJimple.newInstanceOfExpr(local, bType); val node = nodeFactory.createNode(instanceOfExpr, methodSig); assertTrue(node.isEmpty()); @@ -309,8 +310,8 @@ void testInstanceOfExprToNodeConversion() { @Test void testLeExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val leExpr = JavaJimple.newLeExpr(local1, local2); val node = nodeFactory.createNode(leExpr, methodSig); assertTrue(node.isEmpty()); @@ -319,7 +320,7 @@ void testLeExprToNodeConversion() { @Test void testLengthExprToNodeConversion() { val arrayType = ArrayType.createArrayType(aType, 1); - val arrayLocal = new Local("array", arrayType); + val arrayLocal = Jimple.newLocal("array", arrayType); val lengthExpr = JavaJimple.newLengthExpr(arrayLocal); val node = nodeFactory.createNode(lengthExpr, methodSig); assertTrue(node.isEmpty()); @@ -327,8 +328,8 @@ void testLengthExprToNodeConversion() { @Test void testLtExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val ltExpr = JavaJimple.newLtExpr(local1, local2); val node = nodeFactory.createNode(ltExpr, methodSig); assertTrue(node.isEmpty()); @@ -336,8 +337,8 @@ void testLtExprToNodeConversion() { @Test void testMulExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val mulExpr = JavaJimple.newMulExpr(local1, local2); val node = nodeFactory.createNode(mulExpr, methodSig); assertTrue(node.isEmpty()); @@ -345,8 +346,8 @@ void testMulExprToNodeConversion() { @Test void testNeExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val neExpr = JavaJimple.newNeExpr(local1, local2); val node = nodeFactory.createNode(neExpr, methodSig); assertTrue(node.isEmpty()); @@ -354,7 +355,7 @@ void testNeExprToNodeConversion() { @Test void testNegExprToNodeConversion() { - val local = new Local("a", aType); + val local = Jimple.newLocal("a", aType); val negExpr = JavaJimple.newNegExpr(local); val node = nodeFactory.createNode(negExpr, methodSig); assertTrue(node.isEmpty()); @@ -362,8 +363,8 @@ void testNegExprToNodeConversion() { @Test void testOrExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val orExpr = JavaJimple.newOrExpr(local1, local2); val node = nodeFactory.createNode(orExpr, methodSig); assertTrue(node.isEmpty()); @@ -371,8 +372,8 @@ void testOrExprToNodeConversion() { @Test void testRemExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val remExpr = JavaJimple.newRemExpr(local1, local2); val node = nodeFactory.createNode(remExpr, methodSig); assertTrue(node.isEmpty()); @@ -380,8 +381,8 @@ void testRemExprToNodeConversion() { @Test void testShlExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val shlExpr = JavaJimple.newShlExpr(local1, local2); val node = nodeFactory.createNode(shlExpr, methodSig); assertTrue(node.isEmpty()); @@ -389,8 +390,8 @@ void testShlExprToNodeConversion() { @Test void testShrExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val shrExpr = JavaJimple.newShrExpr(local1, local2); val node = nodeFactory.createNode(shrExpr, methodSig); assertTrue(node.isEmpty()); @@ -398,8 +399,8 @@ void testShrExprToNodeConversion() { @Test void testSubExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val subExpr = JavaJimple.newSubExpr(local1, local2); val node = nodeFactory.createNode(subExpr, methodSig); assertTrue(node.isEmpty()); @@ -407,8 +408,8 @@ void testSubExprToNodeConversion() { @Test void testUshrExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val ushrExpr = JavaJimple.newUshrExpr(local1, local2); val node = nodeFactory.createNode(ushrExpr, methodSig); assertTrue(node.isEmpty()); @@ -416,8 +417,8 @@ void testUshrExprToNodeConversion() { @Test void testXorExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); val xorExpr = JavaJimple.newXorExpr(local1, local2); val node = nodeFactory.createNode(xorExpr, methodSig); assertTrue(node.isEmpty()); @@ -457,8 +458,8 @@ void testMethodTypeToNodeConversion() { @Test void testPhiExprToNodeConversion() { - val local1 = new Local("a", aType); - val local2 = new Local("b", aType); + val local1 = Jimple.newLocal("a", aType); + val local2 = Jimple.newLocal("b", aType); List locals = new java.util.ArrayList<>(); locals.add(local1); locals.add(local2); @@ -480,7 +481,7 @@ void testStringConstantToNodeConversion() { @Test void testGetResult() { - val local = new Local("a", aType); + val local = Jimple.newLocal("a", aType); val varNodeOpt = nodeFactory.createNode(local, methodSig); assertTrue(varNodeOpt.isPresent()); } diff --git a/sootup.spark/src/test/java/sootup/spark/test/options/CustomCallGraphTest.java b/sootup.spark/src/test/java/sootup/spark/test/options/CustomCallGraphTest.java index d73fef4ce2d..4e61bd735a8 100644 --- a/sootup.spark/src/test/java/sootup/spark/test/options/CustomCallGraphTest.java +++ b/sootup.spark/src/test/java/sootup/spark/test/options/CustomCallGraphTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test; import sootup.callgraph.CallGraph; import sootup.callgraph.ClassHierarchyAnalysisAlgorithm; -import sootup.core.jimple.common.Local; +import sootup.core.jimple.Jimple; import sootup.core.signatures.MethodSignature; import sootup.core.types.ClassType; import sootup.spark.Engine; @@ -60,8 +60,8 @@ public void clientSuppliedCallGraphIsUsedInPlaceOfCha() { PointsToAnalysis pta = spark.getPointsToAnalysis(); AllocationNode valueAlloc = SparkTestUtil.alloc(valueType, 2L, mainSig); assertTrue( - pta.reachingObjects(new Local("l2", valueType), mainSig).contains(valueAlloc) - || pta.reachingObjects(new Local("l3", valueType), mainSig).contains(valueAlloc), + pta.reachingObjects(Jimple.newLocal("l2", valueType), mainSig).contains(valueAlloc) + || pta.reachingObjects(Jimple.newLocal("l3", valueType), mainSig).contains(valueAlloc), "PAG built from the client-supplied call graph must still resolve the virtual call"); } diff --git a/sootup.spark/src/test/java/sootup/spark/test/options/OnFlyCallGraphTest.java b/sootup.spark/src/test/java/sootup/spark/test/options/OnFlyCallGraphTest.java index b8044ad199e..377f000d4c4 100644 --- a/sootup.spark/src/test/java/sootup/spark/test/options/OnFlyCallGraphTest.java +++ b/sootup.spark/src/test/java/sootup/spark/test/options/OnFlyCallGraphTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sootup.callgraph.CallGraph; -import sootup.core.jimple.common.Local; +import sootup.core.jimple.Jimple; import sootup.core.signatures.MethodSignature; import sootup.core.types.ClassType; import sootup.spark.Engine; @@ -52,13 +52,14 @@ public void otfMatchesChaForBasic() { AllocationNode newContainer = SparkTestUtil.alloc(containerType, 2L, mainSig); assertEquals( - Collections.singleton(newField), pta.reachingObjects(new Local("l2", fieldType), mainSig)); + Collections.singleton(newField), + pta.reachingObjects(Jimple.newLocal("l2", fieldType), mainSig)); assertEquals( Collections.singleton(newContainer), - pta.reachingObjects(new Local("l3", containerType), mainSig)); + pta.reachingObjects(Jimple.newLocal("l3", containerType), mainSig)); assertEquals( Collections.singleton(newContainer), - pta.reachingObjects(new Local("l4", containerType), mainSig)); + pta.reachingObjects(Jimple.newLocal("l4", containerType), mainSig)); } /** @@ -110,8 +111,8 @@ public void otfDiscoversVirtualCallees() { PointsToAnalysis pta = spark.getPointsToAnalysis(); AllocationNode valueAlloc = SparkTestUtil.alloc(valueType, 2L, mainSig); boolean foundValueReach = - pta.reachingObjects(new Local("l2", valueType), mainSig).contains(valueAlloc) - || pta.reachingObjects(new Local("l3", valueType), mainSig).contains(valueAlloc); + pta.reachingObjects(Jimple.newLocal("l2", valueType), mainSig).contains(valueAlloc) + || pta.reachingObjects(Jimple.newLocal("l3", valueType), mainSig).contains(valueAlloc); assertTrue(foundValueReach, "Value allocation must reach at least one local in main"); } @@ -144,7 +145,7 @@ public void otfSelfReferentialFieldLoadDoesNotThrowConcurrentModificationExcepti AllocationNode newB = SparkTestUtil.alloc(nodeType, 2L, mainSig); AllocationNode newC = SparkTestUtil.alloc(nodeType, 3L, mainSig); - Set reachingX = pta.reachingObjects(new Local("l4", nodeType), mainSig); + Set reachingX = pta.reachingObjects(Jimple.newLocal("l4", nodeType), mainSig); assertTrue(reachingX.containsAll(Set.of(newA, newB, newC)), reachingX::toString); }