Skip to content

Support to throw StringIndexOutOfBoundsException in methods subString and charAt GSoC 2025#133

Open
saifk16 wants to merge 6 commits intoSymbolicPathFinder:runtime-exceptionfrom
saifk16:string-index
Open

Support to throw StringIndexOutOfBoundsException in methods subString and charAt GSoC 2025#133
saifk16 wants to merge 6 commits intoSymbolicPathFinder:runtime-exceptionfrom
saifk16:string-index

Conversation

@saifk16
Copy link

@saifk16 saifk16 commented Aug 29, 2025

This PR supports subString and charAt to properly handle StringIndexOutOfBoundsException

Configuration Updates (jpf-sv-comp)

Added proper handling for search.multiple_errors configuration based on RUNTIME_EXCEPTION_MODE
Enhanced error detection logic to handle ClassNotFoundException scenarios
Improved result classification for SAFE/UNSAFE/UNKNOWN states

charAt
Path 0: Index < 0 → StringIndexOutOfBoundsException
Path 1: Index >= string.length() → StringIndexOutOfBoundsException
Path 2: Valid execution path
Added support for StringBuilder.charAt() operations

substring
Single parameter: 3 paths (index < 0, index > length, valid execution path)
Two parameters: 6 paths (beginIndex < 0, endIndex < 0, endIndex > length, beginIndex > length, endIndex < beginIndex, valid execution path)

Test cases were added for both of the methods in the folder rte and the script was also refined.

@saifk16
Copy link
Author

saifk16 commented Aug 29, 2025

@sohah ma'am this pr is created for the substring() and charAt() support and is ready for a review.
Thanks.

Copy link

@sohah sohah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HI @saifk16 , I have a few comments on this PR. Can you please fix and let me know. Thanks!

grep "java.lang.*Exception" $LOG > /dev/null
JAVA_EXCEPTION=$?

grep "java.lang.ClassNotFoundException" $LOG > /dev/null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not comment on lines 100-106, so I am writting a comment about them here. The conditions within this if-statement could be optimized. I think you should check for assertion, and print unsafe. if that does not match check for no errors and print safe. If we have a java-exception or anything else, this should be unknown. I mean how can we print safe when SPF just raised an exception in the reach safety mode? That doesn't seem right. Can you give me an example of when you want to print a safe when SPF had an exception in the reachsafety mode?

Copy link
Author

@saifk16 saifk16 Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a mistake, thank you for noticing actually I was testing that if condition with the exception things, It will be corrected in the recent commit. Thanks

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done.

Comment on lines +126 to +127
elif [ $CLASS_NOT_FOUND -eq 0 ]; then
echo "UNKNOWN"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is problemetic, how can we have ClassNotFoundException, that just means we are unable to access the class during rutime, so this means there is something wrong in the compilation/build process that we then see this error. This check should not be part of the script, and if we are running into these issues then we should resolve them, because that must be a bug that is causing this to happen.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually when we were checking this was happening two times in the verification tasks:

StaticCharMethods05 because of this Scanner scanner = new Scanner(Verifier.nondetString());

and in radians because of double rad = java.lang.Math.toRadians(deg);

Both of them are not present in the respective classes in the jpf-symbc so because of that we get these errors

gov.nasa.jpf.vm.NoUncaughtExceptionsProperty
java.lang.ClassNotFoundException: class not found: java.lang.NoSuchMethodException!!
	at Main.main(Main.java:34)


====================================================== snapshot #1
thread java.lang.Thread:{id:0,name:main,status:RUNNING,priority:5,isDaemon:false,lockCount:0,suspendCount:0}
  call stack:
	at Main.main(Main.java:34)


====================================================== results
error #1: gov.nasa.jpf.vm.NoUncaughtExceptionsProperty "java.lang.ClassNotFoundException: class not found:..."

Copy link

@sohah sohah Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saifk16 This is another bug that we should not have. Please open an issue with it, but the script should not have this change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello ma'am what should I print then, for these two varification task, SAFE, UNSAFE, UN KNOWN?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saifk16 , have you addressed this yet? I think it should be unknown

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no ma'am this is not addressed till now I was waiting for your reply yes ma'am according to the script it was reported to be unknown earlier but was removed later on, I think I need to specially check for this and report unknown

runtime.exception=true
symbolic.lazy=on
symbolic.jrarrays=true
veritestingMode = 5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please remove this option (veritestingMode), it isn't SPF related.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay ma'am.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

recursiveDepth= 200
singlePathOptimization=true
listener = .symbc.SymbolicListener
symbolic.debug=true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add search.multiple_errors=true and turn off nullPointer.exception?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay ma'am.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +356 to +357
} else
return false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think here you are returning a special variable "bresult" to indicate if support was handled or not. So I would use the same variable name here in this return statement.

Copy link
Author

@saifk16 saifk16 Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the code that was present earlier for charAt was using bresult and the method handleChatAt is set to return a boolean can it be void or should it remain boolean type return ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was also done


assert pc != null;

if(currentChocie == 2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the choices are not handled in order. I would expect to see choice 0, 1 then 2. Please fix that

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for that, will be fixed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +1357 to +1359
} else if (currentChocie == 4) {
if (!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again why the order of the choices are flipped.
Also can you add comments, short ones to describe what each choice do?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be fixed, thanks for noticing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments were added

Comment on lines +1361 to +1375
if(sym_v2 != null) {
if(sym_v1 != null) {
pc._addDet(Comparator.GT, sym_v2, sym_v1);
} else {
int val = s1;
pc._addDet(Comparator.GT, sym_v2, new IntegerConstant(val));
}
} else {
int val2 = s2;
pc._addDet(Comparator.GT, new IntegerConstant(val2), sym_v1);
}
if (!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to think about some code restructuring because that is a lot of code to have in one method.
Also, I am not sure why you need 6 choices (from 0-5)? This must be wrong.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The substring(beginIndex, endIndex) method needs 6 distinct choices to handle all possible StringIndexOutOfBoundsException scenarios:

Exception Scenarios

Choice 0: beginIndex < 0

  • When the starting index is negative
  • Example: "hello".substring(-1, 3) → throws exception

Choice 1: endIndex < 0

  • When the ending index is negative
  • Example: "hello".substring(1, -2) → throws exception

Choice 2: beginIndex > string.length()

  • When starting index exceeds string length
  • Example: "hello".substring(10, 15) → throws exception

Choice 3: endIndex > string.length()

  • When ending index exceeds string length
  • Example: "hello".substring(1, 10) → throws exception

Choice 4: beginIndex > endIndex

  • When start index is greater than end index
  • Example: "hello".substring(3, 1) → throws exception

Choice 5: Default Behaviour

  • Preserves existing code functionality that was present before this exception handling support was added

return handleSubString2(invInst, th);
ChoiceGenerator<?> cg;
if (!th.isFirstStepInsn()) { // first time around
cg = new PCChoiceGenerator(5);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you create a 5 choice but the implementation of the method is using 6 choices. Please check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix that, thank you for looking into this, it should be 6 because of (0-5)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@saifk16
Copy link
Author

saifk16 commented Sep 3, 2025

Hello @sohah ma'am this pr is again available for a review

Copy link

@sohah sohah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saifk16 , I left you some comments on older review comments, and added an extra one. Can you please fix and let me know when you are done. Thanks!

//System.out.println("[SymbolicStringHandler] " + sf.toString());
sf.setOperandAttr(result);
} else if (currentChocie == 4) {
} else if (currentChocie == 4) { // beginIndex > endIndex
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not need this choice, it is already covered with the normal case and will just be unsat.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@saifk16
Copy link
Author

saifk16 commented Sep 5, 2025

Hello @sohah ma'am pr is again available for a review.

Copy link

@sohah sohah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saif, a few comments are attached in this review. In general I think this code can be optimized there are a lot of duplication, can we write it in a better way?

Comment on lines +372 to +373
int s1 = sf.pop();
int s2 = sf.pop();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we see stack operation in the middle of the code for preparing the PC!? this should either be right at the top, or right at the end. I wouldj ust place them right after the else.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done moved next to else


assert pc != null;

if (currentChocie == 0) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why aren't you making comments to explain what each choice does?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +1190 to +1191
int s1 = sf.pop();
int s2 = sf.pop();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this again is happening in the middle of the pc code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +1289 to +1292

int s1 = sf.pop();
int s2 = sf.pop();
int s3 = sf.pop();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this too

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +392 to +404
if(!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
} else {
if(sym_v1 != null) {
pc._addDet(Comparator.LT, sym_v1, new IntegerConstant(0));
} else {
int val = s1;
pc._addDet(Comparator.LT, new IntegerConstant(val), new IntegerConstant(0));
}
if(!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the formatting of your code is off!? I know we need to automate this, but at least the new parts should be written nicely

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +399 to +400
pc._addDet(Comparator.LT, new IntegerConstant(val), new IntegerConstant(0));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to invoke the solver for a concrete constraint? can't you just check this in the code?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done

Comment on lines +416 to +417
pc._addDet(Comparator.GE, new IntegerConstant(val), sym_v2._length());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is another concrete constraint

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually symv2._length() is an integer expression here and it is symbolic val is concrete

Comment on lines +1357 to +1358
pc._addDet(Comparator.LT, new IntegerConstant(val), new IntegerConstant(0));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again concrete constraint

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fixed

Comment on lines +392 to +393
if(!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is SO repeated in every exceptional choice, can we do it only once!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done please check the new method being used, thanks for reviewing ma'am

Comment on lines +401 to +404
if(!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again this is so repeatitive, can we do it only once!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had tried a new way looking for ward at your response

@f-ei8ht
Copy link

f-ei8ht commented Sep 14, 2025

@sohah hello ma'am I contributed again to the handler and fixed the repetition and some code at handleCharAt can you please check it and tell if it wrong or right also I have fixed one of the concrete constraint the other one is a mix (concrete and symbolic both values are used) thanks.

@sohah
Copy link

sohah commented Sep 29, 2025

@saifk16 , I noticed that there is a single comment you haven't replied to it yet. Can you please confirm if it is addressed or not? Thank you

@f-ei8ht
Copy link

f-ei8ht commented Sep 29, 2025

@saifk16 , I noticed that there is a single comment you haven't replied to it yet. Can you please confirm if it is addressed or not? Thank you

Hello @sohah ma'am I have added a comment regarding that left review thanks.

Copy link

@sohah sohah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @saifk16 , thank you for your effort. Here are some more comments, can you please address and let us know.

Comment on lines +433 to +455
} else if (currentChocie == 2) { // previous code valid operation
IntegerExpression result = null;
if (sym_v1 == null) { // operand 0 is concrete
int val = s1;
result = sym_v2._charAt(new IntegerConstant(val));
} else {
if (sym_v2 == null) {
ElementInfo e1 = th.getElementInfo(s2);
String val2 = e1.asString();
sym_v2 = new StringConstant(val2);
result = sym_v2._charAt(sym_v1);
} else {
result = sym_v2._charAt(sym_v1);
}
bresult = true;
//System.out.println("[handleCharAt] Ignoring: " + result.toString());
// th.push(0, false);
}
sf.push(0, false);
sf.setOperandAttr(result);
}
}
return bresult; // not used
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @saifk16 , why this code is like it is new code. it should have the git blame of their previous authors. with this it looks like you've added this code when you did not. Can you please track back to the original owner of this code? thank you.

@@ -1067,9 +1150,25 @@ public Instruction handleReplace(JVMInvokeInstruction invInst, ThreadInfo th) {
public Instruction handleSubString(JVMInvokeInstruction invInst, ThreadInfo th) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unable to make a comment on line 976 that checks for the null case, I think if we are in this choice we should be safe and we should not be checking for null option under these cases, if we do then we are duplicating other work that we've done elsewhere. This comment also applies on lin 1002

@@ -1067,9 +1150,25 @@ public Instruction handleReplace(JVMInvokeInstruction invInst, ThreadInfo th) {
public Instruction handleSubString(JVMInvokeInstruction invInst, ThreadInfo th) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, i am unable to comment on line 1040, why here when both are symbolic, you are only checking one of them not both?

Comment on lines +1154 to +1165
if (!th.isFirstStepInsn()) { // first time around
cg = new PCChoiceGenerator(3);
th.getVM().setNextChoiceGenerator(cg);
return invInst;
} else {
handleSubString1(invInst, th);
return invInst.getNext(th);
}
} else {
return handleSubString2(invInst, th);
ChoiceGenerator<?> cg;
if (!th.isFirstStepInsn()) { // first time around
cg = new PCChoiceGenerator(5);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you insert comments why you need 3 choices versus 5 choices, it isn't clear when one gets to read it for the first time.

Comment on lines +1316 to +1401

if (currentChocie == 0) { // beginIndex is less than zero
if (!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
} else {
if (sym_v2 != null) {
pc._addDet(Comparator.LT, sym_v2, new IntegerConstant(0));
if (!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
}
} else {
if (s2 < 0) {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
} else {
th.getVM().getSystemState().setIgnored(true);
}
}
}
} else if (currentChocie == 1) { // beginIndex is greater than the length of string
if (!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
} else {
if (sym_v3 != null) {
if (sym_v2 != null) {
pc._addDet(Comparator.GT, sym_v2, sym_v3._length());
} else {
int val = s2;
pc._addDet(Comparator.GT, new IntegerConstant(val), sym_v3._length());
}
} else {
ElementInfo e1 = th.getElementInfo(s3);
String val2 = e1.asString();
sym_v3 = new StringConstant(val2);
pc._addDet(Comparator.GT, sym_v2, sym_v3._length());
}
if (!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
}
}
} else if (currentChocie == 2) { // endIndex is less than zero
if (!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
} else {
if (sym_v1 != null) {
pc._addDet(Comparator.LT, sym_v1, new IntegerConstant(0));
if (!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
}
} else {
if (s1 < 0) {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
} else {
th.getVM().getSystemState().setIgnored(true);
}
}
}
} else if (currentChocie == 3) { // endIndex is greater than tha length of the string
if (!rte_flag) {
th.getVM().getSystemState().setIgnored(true);
} else {
if (sym_v3 != null) {
if (sym_v1 != null) {
pc._addDet(Comparator.GT, sym_v1, sym_v3._length());
} else {
int val = s1;
pc._addDet(Comparator.GT, new IntegerConstant(val), sym_v3._length());
}
} else {
ElementInfo e1 = th.getElementInfo(s3);
String val2 = e1.asString();
sym_v3 = new StringConstant(val2);
pc._addDet(Comparator.GT, sym_v1, sym_v3._length());
}
if (!pc.simplify()) {
th.getVM().getSystemState().setIgnored(true);
} else {
th.createAndThrowException("java.lang.StringIndexOutOfBoundsException");
}
}
} else if (currentChocie == 4) { // previous code, valid operation
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my comment on handleSubstring1 and handleSubstring2 is that there are many redundant code which I think can be grouped together for nicer presentation and understanding, like this it is just too repeatitive.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, the old code should have the names of their authors, right now, they appear with your name, can we restore the old author's names?

@f-ei8ht
Copy link

f-ei8ht commented Nov 30, 2025

okay @sohah ma'am I will restore to the old author names, thanks for your review I will also add some comments and let you know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants