Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/bodyinterceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,46 @@ Example:

![SSA Example_2](assets/figures/SSA%20Example_2.png)

In the given example, the StaticSingleAssignmentFormer assigns each`IdentityStmt`and`AssignStmt`to a new local variable . And each use uses the local variable which is most recently defined. Sometimes, it is impossible to determine the most recently defined local variable for a use in a join block. In this case, the StaticSingleAssignmentFormer will insert a`PhiStmt`in the front of the join block to merge all most recently defined local variables and assign them a new local variable.
In the given example, the StaticSingleAssignmentFormer assigns each`IdentityStmt`and`AssignStmt`to a new local variable . And each use uses the local variable which is most recently defined. Sometimes, it is impossible to determine the most recently defined local variable for a use in a join block. In this case, the StaticSingleAssignmentFormer will insert a`PhiStmt`in the front of the join block to merge all most recently defined local variables and assign them a new local variable.

## Preserving Debug Metadata During Mutations

When `AnalysisExtendedScope.LocalVariableTable` is enabled, Jimple statements carry local variable debug scopes (`LocalVariableScope`) attached to `StmtPositionInfo`. Interceptors that mutate or replace statements should preserve or transfer this metadata depending on the transformation.

### Preserving existing metadata with position replacement

To update the source position (e.g., line number) of an existing statement while preserving its attached debug metadata (such as variable scopes and operand coordinates), use `StmtPositionInfo.withStmtPosition(...)`:

```java
// Preserves existing scopes and operand positions while updating the statement position
Stmt updated = stmt.withPositionInfo(
stmt.getPositionInfo().withStmtPosition(newPosition));
```

### Adopting metadata from another statement

When transforming or replacing statements, distinguish between adopting the entire metadata versus adopting only specific attributes:

- **Adopting entire metadata (Position and Scope)**:
When a new statement replaces an original statement entirely:
```java
// Replaces both position and debug variable scope with sourceStmt's metadata
Stmt replacement = newStmt.withPositionInfo(sourceStmt.getPositionInfo());
```

- **Adopting position while preserving current scope (e.g., inlining / aggregation)**:
When a computation from a definition is inlined into a use site, the destination's in-scope variable bindings should be kept while attributing the source line of the definition:
```java
// Keeps useStmt's variable scope, but attributes defStmt's line position
Stmt aggregated = useStmt.withPositionInfo(
useStmt.getPositionInfo().withStmtPosition(defStmt.getPositionInfo().getStmtPosition()));
```

- **Adopting scope while preserving current position**:
To transfer only the variable scope from another statement:
```java
// Adopts variable scope from otherStmt, keeping currentStmt's position
Stmt updated = LocalVariableStmtPositionInfo.withLocalVariables(
currentStmt, LocalVariableStmtPositionInfo.getLocalVariables(otherStmt));
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sootup.core.inputlocation;

/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2019-2026 SootUp contributors
* %%
* 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
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
* #L%
*/

import sootup.core.model.LocalVariableScope;

/** Extended analysis scope options for an {@link AnalysisInputLocation}. */
public enum AnalysisExtendedScope {
/**
* Preserves LocalVariableTable debug metadata from bytecode, creating statement-level {@link
* LocalVariableScope} metadata on Jimple statements.
*/
LocalVariableTable
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
* #L%
*/

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.jspecify.annotations.NonNull;
import sootup.core.frontend.SootClassSource;
Expand Down Expand Up @@ -74,6 +76,12 @@ public interface AnalysisInputLocation extends AutoCloseable {

@NonNull List<BodyInterceptor> getBodyInterceptors();

/** Returns the set of extended scope features enabled for this input location */
@NonNull
default Set<AnalysisExtendedScope> getExtendedScope() {
return Collections.emptySet();
}

/** Release any file-system resources held by this input location (e.g. open ZipFileSystems). */
default void close() throws Exception {}
}
5 changes: 5 additions & 0 deletions sootup.core/src/main/java/sootup/core/jimple/Jimple.java
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,11 @@ public static Local newLocal(String name, Type t) {
return new Local(name, t);
}

/** Constructs a Local with the given name, type, and bytecode slot index. */
public static Local newLocal(String name, Type t, int slotIndex) {
return new Local(name, t, slotIndex);
}

/** Constructs a JStaticFieldRef(FieldSignature) grammar chunk. */
public static JStaticFieldRef newStaticFieldRef(FieldSignature f) {
return new JStaticFieldRef(f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ public String toString() {
}

@NonNull
public StmtPositionInfo withStmtPosition(@NonNull Position stmtPosition) {
@Override
public FullStmtPositionInfo withStmtPosition(@NonNull Position stmtPosition) {
return new FullStmtPositionInfo(stmtPosition, operandPositions);
}

@NonNull
public StmtPositionInfo withOperandPositions(@NonNull Position[] operandPositions) {
public FullStmtPositionInfo withOperandPositions(@NonNull Position[] operandPositions) {
return new FullStmtPositionInfo(stmtPosition, operandPositions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package sootup.core.jimple.basic;

/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2019-2023 Linghui Luo, 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
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
* #L%
*/

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.LocalVariableScope;
import sootup.core.model.Position;

/** Position information with a captured, shared immutable debug variable scope. */
public interface LocalVariableStmtPositionInfo extends StmtPositionInfo {

@NonNull LocalVariableScope getLocalVariables();

/** Returns the same coordinate variant with a different captured scope. */
@NonNull LocalVariableStmtPositionInfo withLocalVariables(@NonNull LocalVariableScope scope);

@Nullable
static LocalVariableScope getLocalVariables(@Nullable StmtPositionInfo info) {
if (info instanceof LocalVariableStmtPositionInfo lv) {
return lv.getLocalVariables();
}
return null;
}

@Nullable
static LocalVariableScope getLocalVariables(@Nullable Stmt stmt) {
if (stmt != null && stmt.getPositionInfo() instanceof LocalVariableStmtPositionInfo lv) {
return lv.getLocalVariables();
}
return null;
}

@NonNull
static StmtPositionInfo withStmtPositionInfo(
@NonNull StmtPositionInfo stmt, @Nullable LocalVariableScope scope) {
if (scope == null) {
if (stmt instanceof Full full) {
return new FullStmtPositionInfo(full.stmtPosition, full.operandPositions);
}
if (stmt instanceof Simple simple) {
return new SimpleStmtPositionInfo(simple.stmtPosition);
}
return stmt;
}
if (stmt instanceof LocalVariableStmtPositionInfo lv) {
return lv.withLocalVariables(scope);
}
if (stmt instanceof FullStmtPositionInfo full) {
return new Full(full, scope);
}
return new Simple(stmt.getStmtPosition(), scope);
}

@NonNull
static Stmt withLocalVariables(@NonNull Stmt stmt, @Nullable LocalVariableScope scope) {
StmtPositionInfo current = stmt.getPositionInfo();
StmtPositionInfo updated = withStmtPositionInfo(current, scope);
return current == updated ? stmt : stmt.withPositionInfo(updated);
}

/** LocalVariable + FullStmtPositionInfo */
class Full extends FullStmtPositionInfo implements LocalVariableStmtPositionInfo {
@NonNull protected final LocalVariableScope scope;

public Full(@NonNull FullStmtPositionInfo stmt, @NonNull LocalVariableScope scope) {
this(stmt.stmtPosition, stmt.operandPositions, scope);
}

public Full(
@NonNull Position stmtPosition,
@NonNull Position[] operandPositions,
@NonNull LocalVariableScope scope) {
super(stmtPosition, operandPositions);
this.scope = scope;
}

@Override
public @NonNull LocalVariableScope getLocalVariables() {
return scope;
}

@Override
public @NonNull LocalVariableStmtPositionInfo withLocalVariables(
@NonNull LocalVariableScope scope) {
return new Full(stmtPosition, operandPositions, scope);
}

@NonNull
@Override
public Full withStmtPosition(@NonNull Position stmtPosition) {
return new Full(stmtPosition, operandPositions, scope);
}

@NonNull
@Override
public Full withOperandPositions(@NonNull Position[] operandPositions) {
return new Full(stmtPosition, operandPositions, scope);
}
}

/** LocalVariable + SimpleStmtPositionInfo */
class Simple extends SimpleStmtPositionInfo implements LocalVariableStmtPositionInfo {
@NonNull protected final LocalVariableScope scope;

public Simple(@NonNull Position stmtPosition, @NonNull LocalVariableScope scope) {
super(stmtPosition);
this.scope = scope;
}

@Override
public @NonNull LocalVariableScope getLocalVariables() {
return scope;
}

@Override
public @NonNull LocalVariableStmtPositionInfo withLocalVariables(
@NonNull LocalVariableScope scope) {
return new Simple(stmtPosition, scope);
}

@NonNull
@Override
public Simple withStmtPosition(@NonNull Position stmtPosition) {
return new Simple(stmtPosition, scope);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@
*
* @author Markus Schmidt
*/
public class SimpleStmtPositionInfo extends StmtPositionInfo {
public class SimpleStmtPositionInfo implements StmtPositionInfo {

static StmtPositionInfo NOPOSITION =
new SimpleStmtPositionInfo(NoPositionInformation.getInstance()) {
@Override
public String toString() {
return "No StmtPositionnfo";
}
};

@NonNull protected final Position stmtPosition;

Expand All @@ -56,9 +64,22 @@ public Position getStmtPosition() {
return stmtPosition;
}

@NonNull
@Override
public SimpleStmtPositionInfo withStmtPosition(@NonNull Position stmtPosition) {
return new SimpleStmtPositionInfo(stmtPosition);
}

@Nullable
@Override
public Position getOperandPosition(int index) {
return null;
}

@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("stmt at:").append(getStmtPosition());
return s.toString();
}
}
Loading