Skip to content

Commit

Permalink
add symbol table for local vars
Browse files Browse the repository at this point in the history
  • Loading branch information
LoiNguyenCS committed Oct 23, 2023
1 parent 8b3b5f7 commit bae6d74
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 6 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/checkerframework/specimin/UnsolvedMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public void setReturnType(@ClassGetSimpleName String returnType) {
public @ClassGetSimpleName String getReturnType() {
return returnType;
}

/**
* Get the name of this method
*
* @return the name of this method
*/
public String getName() {
return name;
}

/**
* Return the content of the method. Note that the body of the method is stubbed out.
*
Expand Down
142 changes: 136 additions & 6 deletions src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.expr.SimpleName;
import com.github.javaparser.ast.expr.SwitchExpr;
import com.github.javaparser.ast.stmt.CatchClause;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.ast.stmt.ForStmt;
import com.github.javaparser.ast.stmt.IfStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.stmt.SwitchEntry;
import com.github.javaparser.ast.stmt.TryStmt;
import com.github.javaparser.ast.stmt.WhileStmt;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
Expand All @@ -42,6 +50,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
Expand All @@ -66,6 +75,8 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor<Void> {
/** The package of this class */
private String currentPackage = "";

private Stack<HashSet<String>> localVariables = new Stack<>();

/** The simple name of the class currently visited */
private @ClassGetSimpleName String className = "";

Expand Down Expand Up @@ -298,8 +309,103 @@ public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) {
}
}

@Override
public Visitable visit(ForStmt node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
@SuppressWarnings(
"nullness") // this method could return null, and the setElstStmt method could accept a null
// value as parameter. So this SupressWarnings is to avoid the complains of
// CheckerFramework
public Visitable visit(IfStmt n, Void arg) {
HashSet<String> localVarInCon = new HashSet<>();
localVariables.push(localVarInCon);
Expression condition = (Expression) n.getCondition().accept(this, arg);
localVariables.pop();
localVarInCon = new HashSet<>();
localVariables.push(localVarInCon);
Statement elseStmt = n.getElseStmt().map(s -> (Statement) s.accept(this, arg)).orElse(null);
localVariables.pop();
localVarInCon = new HashSet<>();
localVariables.push(localVarInCon);
Statement thenStmt = (Statement) n.getThenStmt().accept(this, arg);
localVariables.pop();
if (condition == null || thenStmt == null) return null;
n.setCondition(condition);
n.setElseStmt(elseStmt);
n.setThenStmt(thenStmt);
return n;
}

@Override
public Visitable visit(WhileStmt node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
public Visitable visit(SwitchExpr node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
public Visitable visit(SwitchEntry node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
public Visitable visit(TryStmt node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
public Visitable visit(CatchClause node, Void p) {
HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, p);
localVariables.pop();
return node;
}

@Override
public Visitable visit(VariableDeclarator decl, Void p) {
// if there is no list of local variables, then the current VariableDeclarator visited is a
// field declaration
if (this.localVariables.size() != 0) {
HashSet<String> currentListOfLocals = localVariables.pop();
currentListOfLocals.add(decl.getName().asString());
localVariables.push(currentListOfLocals);
}
return super.visit(decl, p);
}

@Override
public Visitable visit(NameExpr node, Void arg) {
String fieldName = node.getNameAsString();
if (fieldAndItsClass.containsKey(fieldName)) {
return super.visit(node, arg);
}
// This method explicitly handles NameExpr instances that represent fields of classes but are
// not explicitly shown in the code. For example, if "number" is a field of a class, then
// "return number;" is an expression that this method will address. If the NameExpr instance is
Expand All @@ -311,9 +417,15 @@ public Visitable visit(NameExpr node, Void arg) {
if (parentNode.isEmpty()
|| !(parentNode.get() instanceof MethodCallExpr
|| parentNode.get() instanceof FieldAccessExpr)) {
String fieldName = node.getNameAsString();
if (!fieldAndItsClass.containsKey(fieldName)
|| !fieldAndItsClass.get(fieldName).equals(className)) {
boolean isALocalVar = false;
Iterator<HashSet<String>> value = localVariables.iterator();
while (value.hasNext()) {
if (value.next().contains(fieldName)) {
isALocalVar = true;
break;
}
}
if (!isALocalVar) {
updateSyntheticClassForSuperCall(node);
}
}
Expand Down Expand Up @@ -361,7 +473,12 @@ public Visitable visit(MethodDeclaration node, Void arg) {
classAndPackageMap.getOrDefault(nodeTypeSimpleForm, this.chosenPackage));
this.updateMissingClass(syntheticType);
}
return super.visit(node, arg);

HashSet<String> currentLocalVariables = new HashSet<>();
localVariables.push(currentLocalVariables);
super.visit(node, arg);
localVariables.pop();
return node;
}

@Override
Expand Down Expand Up @@ -441,7 +558,7 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) {
this.gotException = true;
try {
List<String> argumentsCreation = getArgumentsFromObjectCreation(newExpr);
UnsolvedMethod creationMethod = new UnsolvedMethod(type, "", argumentsCreation);
UnsolvedMethod creationMethod = new UnsolvedMethod("", type, argumentsCreation);
UnsolvedClass newClass =
new UnsolvedClass(type, classAndPackageMap.getOrDefault(type, this.chosenPackage));
newClass.addMethod(creationMethod);
Expand Down Expand Up @@ -902,11 +1019,24 @@ public void updateMissingClass(UnsolvedClass missedClass) {
while (iterator.hasNext()) {
UnsolvedClass e = iterator.next();
if (e.getClassName().equals(missedClass.getClassName())) {

// add new methods
for (UnsolvedMethod method : missedClass.getMethods()) {
if (!method.getReturnType().equals("")) {
boolean alreadyHad = false;
for (UnsolvedMethod classMethod : e.getMethods()) {
if (classMethod.getReturnType().equals(method.getReturnType())) {
if (classMethod.getName().equals(method.getName())) {
alreadyHad = true;
break;
}
}
}
if (!alreadyHad) {
e.addMethod(method);
}
}

// add new fields
for (String variablesDescription : missedClass.getClassFields()) {
e.addFields(variablesDescription);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.checkerframework.specimin;

import java.io.IOException;
import org.junit.Test;

/**
* This test checks that if Specimin will work properly where there is a super variables call while
* the parent class file is not in the root directory physically
*/
public class HiddenSuperFieldAndUnsolvedLocal {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"HiddenSuperFieldAndUnsolvedLocal",
new String[] {"com/example/Child.java"},
new String[] {"com.example.Child#returnLocalName()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example;

import sample.pack.MyType;

public class Child extends Parent {

public MyType returnLocalName() {
if (3 > 4) {
MyType thisName = new MyType();
return thisName;
} else if (4 > 7) {
MyType thatName = new MyType();
return thatName;
} else {
return theName;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example;

public class Parent {

public MyType theName = null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package sample.pack;

public class MyType {

public MyType() {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example;

import sample.pack.MyType;

public class Child extends Parent {
// for Specimin, an implicit called superfield and a variable with an unsolved type will both be seen as unsolved NameExpr instances. This test is to make sure that Specimin will not confuse between those two cases.
public MyType returnLocalName() {
if (3 > 4) {
MyType thisName = new MyType();
return thisName;
} else if (4 > 7) {
MyType thatName = new MyType();
return thatName;
} else {
return theName;
}
}
}

0 comments on commit bae6d74

Please sign in to comment.