Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0572bdf
add wrapper around FieldModifier for validation
sahilagichani14 Apr 18, 2025
a190f4b
added test for FieldModifiersValidator
sahilagichani14 Apr 18, 2025
9780fa6
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
swissiety May 27, 2025
8f5848e
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
sahilagichani14 May 30, 2025
0f81035
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
sahilagichani14 May 30, 2025
26b2c4e
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
sahilagichani14 Jun 3, 2025
eeee87a
Merge remote-tracking branch 'origin/794-check-for-valid-modifier-com…
sahilagichani14 Jun 3, 2025
f462e98
implement BodyValidationInterceptor, ClassValidationInterceptor
sahilagichani14 Jun 6, 2025
bafb3b2
missed method param
sahilagichani14 Jun 6, 2025
ac63486
implement InvokeArgumentValidator
sahilagichani14 Jun 10, 2025
b3ea18f
implement CheckEscapingValidator
sahilagichani14 Jun 11, 2025
7211837
implement CheckEscapingValidator
sahilagichani14 Jun 11, 2025
4732cc9
implement InvokeValidator
sahilagichani14 Jun 11, 2025
e769aa1
implement ReturnStatementsValidator
sahilagichani14 Jun 11, 2025
ab2f192
implement CheckTypesValidator && enable them by default
sahilagichani14 Jun 19, 2025
9ea4757
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
sahilagichani14 Jun 19, 2025
4e4d842
fix license header failure && locale pkgName
sahilagichani14 Jun 19, 2025
e2048db
fix mvn com.spotify.fmt:fmt-maven-plugin:format
sahilagichani14 Jun 19, 2025
5e0184b
fix ClassValidationInterceptor
sahilagichani14 Jun 19, 2025
5cea9f1
disable bydefault validation interceptors
sahilagichani14 Jun 19, 2025
5bc5ed3
Merge branch 'develop' into 794-check-for-valid-modifier-combinations
sahilagichani14 Jul 8, 2025
1628711
Merge remote-tracking branch 'origin/develop' into 794-check-for-vali…
stschott Oct 22, 2025
4388eff
add test cases for validators
stschott Oct 23, 2025
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
12 changes: 2 additions & 10 deletions sootup.core/src/main/java/sootup/core/model/FieldModifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@
* #L%
*/

import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.jspecify.annotations.NonNull;

/**
* An Enum that provides static methods and constants to represent and work with with Java modifiers
* (ie public, final,...) Represents Java modifiers that can be packed and combined via EnumSet and
* An Enum that provides static methods and constants to represent and work with Java modifiers (ie
* public, final,...) Represents Java modifiers that can be packed and combined via EnumSet and
* methods to query these.
*/
public enum FieldModifier {
Expand Down Expand Up @@ -133,12 +131,6 @@ public static String toString(@NonNull Set<FieldModifier> m) {
return builder.toString();
}

@NonNull
// depends on the natural order of the Enums!
Comment thread
swissiety marked this conversation as resolved.
public static String toString(@NonNull EnumSet<FieldModifier> m) {
return m.stream().map((mod) -> mod.name().toLowerCase()).collect(Collectors.joining(" "));
}

/**
* @return the bytecode of this Modifier.
*/
Expand Down
12 changes: 12 additions & 0 deletions sootup.core/src/main/java/sootup/core/types/Type.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ public static boolean isIntLikeType(Type type) {
*/
}

/** Converts the int-like types (short, byte, boolean and char) to IntType. */
public static Type toMachineType(Type t) {
if (t.equals(PrimitiveType.ShortType.getInstance())
|| t.equals(PrimitiveType.ByteType.getInstance())
|| t.equals(PrimitiveType.BooleanType.getInstance())
|| t.equals(PrimitiveType.CharType.getInstance())) {
return PrimitiveType.IntType.getInstance();
} else {
return t;
}
}

public static boolean isObject(Type type) {
return type.toString().equals("java.lang.Object");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import sootup.core.views.View;

/** Implement this interface if you want to provide your own body Validator */
public interface BodyValidator {
public interface BodyValidator extends Validator {
/**
* Validates the given body and saves all validation errors in the given list.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package sootup.core.validation;

/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2025 Sahil Agichani
* %%
* 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 java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import sootup.core.jimple.common.expr.AbstractInvokeExpr;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.Body;
import sootup.core.signatures.MethodSignature;
import sootup.core.types.Type;
import sootup.core.views.View;

public class CheckEscapingValidator implements BodyValidator {

@Override
public List<ValidationException> validate(Body body, View view) {
List<ValidationException> validationException = new ArrayList<>();

for (Stmt stmt : body.getStmts()) {
if (stmt.isInvokableStmt()) {
Optional<AbstractInvokeExpr> invokeExprOpt = stmt.asInvokableStmt().getInvokeExpr();
if (invokeExprOpt.isPresent()) {
MethodSignature ref = invokeExprOpt.get().getMethodSignature();
if (ref.getName().contains("'")
|| ref.getDeclClassType().getFullyQualifiedName().contains("'")) {
validationException.add(
new ValidationException(stmt, "Escaped name found in signature"));
}
for (Type paramType : ref.getParameterTypes()) {
if (paramType.toString().contains("'")) {
validationException.add(
new ValidationException(stmt, "Escaped name found in signature"));
}
}
}
}
}
return validationException;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,73 +22,173 @@
* #L%
*/

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import sootup.core.jimple.common.expr.AbstractInstanceInvokeExpr;
import sootup.core.jimple.common.expr.AbstractInvokeExpr;
import sootup.core.jimple.common.ref.JCaughtExceptionRef;
import sootup.core.jimple.common.stmt.AbstractDefinitionStmt;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.Body;
import sootup.core.model.SootClass;
import sootup.core.signatures.MethodSignature;
import sootup.core.typehierarchy.ViewTypeHierarchy;
import sootup.core.types.*;
import sootup.core.views.View;

public class CheckTypesValidator implements BodyValidator {

@Override
public List<ValidationException> validate(Body body, View view) {
// TODO: check code from old soot in the comment below
/*
* for (Unit u : body.getUnits()) { String errorSuffix = " at " + u + " in " + body.getMethod();
*
* if (u instanceof DefinitionStmt) { DefinitionStmt astmt = (DefinitionStmt) u; if (!(astmt.getRightOp() instanceof
* CaughtExceptionRef)) { Type leftType = Type.toMachineType(astmt.getLeftOp().getType()); Type rightType =
* Type.toMachineType(astmt.getRightOp().getType());
*
* checkCopy(astmt, body, exception, leftType, rightType, errorSuffix); } }
*
* if (u instanceof Stmt) { Stmt stmt = (Stmt) u; if (stmt.containsInvokeExpr()) { SootMethodRef called =
* stmt.getInvokeExpr().getMethodRef(); InvokeExpr iexpr = stmt.getInvokeExpr();
*
* if (iexpr instanceof InstanceInvokeExpr) { InstanceInvokeExpr iiexpr = (InstanceInvokeExpr) iexpr; checkCopy(stmt,
* body, exception, called.declaringClass().getType(), iiexpr.getBase().getType(), " in receiver of call" + errorSuffix);
* }
*
* if (called.parameterTypes().size() != iexpr.getArgCount()) { exception.add(new ValidationException(stmt,
* "Argument count does not match the signature of the called function",
* "Warning: Argument count doesn't match up with signature in call" + errorSuffix + " in " + body.getMethod())); } else
* { for (int i = 0; i < iexpr.getArgCount(); i++) { checkCopy(stmt, body, exception,
* Type.toMachineType(called.parameterType(i)), Type.toMachineType(iexpr.getArg(i).getType()), " in argument " + i +
* " of call" + errorSuffix + " (Note: Parameters are zero-indexed)"); } } } } } }
*
* private void checkCopy(Unit stmt, Body body, List<ValidationException> exception, Type leftType, Type rightType,
* String errorSuffix) {
*
*
* if (leftType instanceof PrimType || rightType instanceof PrimType) { if (leftType instanceof IntType && rightType
* instanceof IntType) { return; } if (leftType instanceof LongType && rightType instanceof LongType) { return; } if
* (leftType instanceof FloatType && rightType instanceof FloatType) { return; } if (leftType instanceof DoubleType &&
* rightType instanceof DoubleType) { return; } exception.add( new ValidationException(stmt, "",
* "Warning: Bad use of primitive type" + errorSuffix + " in " + body.getMethod())); }
*
* if (rightType instanceof NullType) { return; } if (leftType instanceof RefType && ((RefType)
* leftType).getClassName().equals("java.lang.Object")) { return; }
*
* if (leftType instanceof ArrayType || rightType instanceof ArrayType) { if (leftType instanceof ArrayType && rightType
* instanceof ArrayType) { return; } // it is legal to assign arrays to variables of type Serializable, // Cloneable or
* Object if (rightType instanceof ArrayType) { if (leftType.equals(RefType.v("java.io.Serializable")) ||
* leftType.equals(RefType.v("java.lang.Cloneable")) || leftType.equals(RefType.v("java.lang.Object"))) { return; } }
*
* exception .add(new ValidationException(stmt, "Warning: Bad use of array type" + errorSuffix + " in " +
* body.getMethod())); }
*
* if (leftType instanceof RefType && rightType instanceof RefType) { SootClass leftClass = ((RefType)
* leftType).getSootClass(); SootClass rightClass = ((RefType) rightType).getSootClass(); if (leftClass.isPhantom() ||
* rightClass.isPhantom()) { return; }
*
* if (leftClass.isInterface()) { if (rightClass.isInterface()) { if (!(leftClass.getName().equals(rightClass.getName())
* || Scene.v().getActiveHierarchy().isInterfaceSubinterfaceOf(rightClass, leftClass))) { exception.add(new
* ValidationException(stmt, "Warning: Bad use of interface type" + errorSuffix + " in " + body.getMethod())); } } else {
* // No quick way to check this for now. } } else { if (rightClass.isInterface()) { exception.add(new
* ValidationException(stmt, "Warning: trying to use interface type where non-Object class expected" + errorSuffix +
* " in " + body.getMethod())); } else { if (!Scene.v().getActiveHierarchy().isClassSubclassOfIncluding(rightClass,
* leftClass)) { exception.add( new ValidationException(stmt, "Warning: Bad use of class type" + errorSuffix + " in " +
* body.getMethod())); } } } return; } exception.add(new ValidationException(stmt, "Warning: Bad types" + errorSuffix +
* " in " + body.getMethod()));
*/
return null;

List<ValidationException> validationException = new ArrayList<>();
final String methodSuffix = " in " + body.getMethodSignature();
for (Stmt stmt : body.getStmts()) {
String errorSuffix = " at " + stmt + methodSuffix;

if (stmt instanceof AbstractDefinitionStmt) {
AbstractDefinitionStmt astmt = (AbstractDefinitionStmt) stmt;
if (!(astmt.getRightOp() instanceof JCaughtExceptionRef)) {
Type leftType = Type.toMachineType(astmt.getLeftOp().getType());
Type rightType = Type.toMachineType(astmt.getRightOp().getType());

checkCopy(astmt, validationException, leftType, rightType, errorSuffix, view);
}
}
if (stmt.isInvokableStmt()) {
Optional<AbstractInvokeExpr> invokeExpr = stmt.asInvokableStmt().getInvokeExpr();
if (invokeExpr.isPresent()) {
AbstractInvokeExpr iexpr = invokeExpr.get();
MethodSignature called = iexpr.getMethodSignature();

if (iexpr instanceof AbstractInstanceInvokeExpr) {
checkCopy(
stmt,
validationException,
called.getDeclClassType(),
iexpr.getType(),
" in receiver of call" + errorSuffix,
view);
}

final int argCount = iexpr.getArgCount();
if (called.getParameterTypes().size() != argCount) {
validationException.add(
new ValidationException(
stmt,
"Argument count does not match the signature of the called function"
+ "Warning: Argument count doesn't match up with signature in call"
+ errorSuffix));
} else {
for (int i = 0; i < argCount; i++) {
checkCopy(
stmt,
validationException,
Type.toMachineType(called.getParameterType(i)),
Type.toMachineType(iexpr.getArg(i).getType()),
" in argument "
+ i
+ " of call"
+ errorSuffix
+ " (Note: Parameters are zero-indexed)",
view);
}
}
}
}
}
return validationException;
}

private void checkCopy(
Stmt stmt,
List<ValidationException> exception,
Type leftType,
Type rightType,
String errorSuffix,
View view) {
final ClassType objectClassType = view.getIdentifierFactory().getClassType("java.lang.Object");
final ClassType serializableClassType =
view.getIdentifierFactory().getClassType("java.io.Serializable");
final ClassType cloneableClassType =
view.getIdentifierFactory().getClassType("java.lang.Cloneable");

if (leftType instanceof PrimitiveType || rightType instanceof PrimitiveType) {
if ((leftType instanceof PrimitiveType.IntType && rightType instanceof PrimitiveType.IntType)
|| (leftType instanceof PrimitiveType.LongType
&& rightType instanceof PrimitiveType.LongType)) {
return;
}
if (leftType instanceof PrimitiveType.FloatType
&& rightType instanceof PrimitiveType.FloatType) {
return;
}
if (leftType instanceof PrimitiveType.DoubleType
&& rightType instanceof PrimitiveType.DoubleType) {
return;
}

return;
}

if ((rightType instanceof NullType)
|| (leftType instanceof ReferenceType
&& objectClassType.getFullyQualifiedName().equals(leftType.toString()))) {
return;
}

if (leftType instanceof ArrayType || rightType instanceof ArrayType) {
if (leftType instanceof ArrayType && rightType instanceof ArrayType) {
return;
}
// it is legal to assign arrays to variables of type Serializable, Cloneable or Object
if (rightType instanceof ArrayType) {
if (leftType.equals(serializableClassType)
|| leftType.equals(cloneableClassType)
|| leftType.equals(objectClassType)) {
return;
}
}

exception.add(new ValidationException(stmt, "Warning: Bad use of array type" + errorSuffix));
return;
}

if (leftType instanceof ReferenceType && rightType instanceof ReferenceType) {
ViewTypeHierarchy viewTypeHierarchy = new ViewTypeHierarchy(view);
SootClass leftClass = view.getClass((ClassType) leftType).orElse(null);
SootClass rightClass = view.getClass((ClassType) rightType).orElse(null);
if (leftClass != null && rightClass != null) {
if (leftClass.isInterface()) {
if (rightClass.isInterface()) {
boolean interfaceSubinterfaceOf =
viewTypeHierarchy
.subinterfacesOf((ClassType) leftType)
.anyMatch(x -> x.equals(rightType));
if (!(leftClass.getName().equals(rightClass.getName()) || interfaceSubinterfaceOf)) {
exception.add(
new ValidationException(
stmt, "Warning: Bad use of interface type" + errorSuffix));
}
} else {
// No quick way to check this for now.
}
} else if (rightClass.isInterface()) {
exception.add(
new ValidationException(
stmt,
"Warning: trying to use interface type where non-Object class expected"
+ errorSuffix));
} else if (viewTypeHierarchy
.subclassesOf((ClassType) leftType)
.noneMatch(x -> x.equals(rightType))) {
exception.add(
new ValidationException(stmt, "Warning: Bad use of class type" + errorSuffix));
}
return;
}
}
exception.add(new ValidationException(stmt, "Warning: Bad types" + errorSuffix));
}
}
Loading