Skip to content

Commit affbbc2

Browse files
authored
Merge pull request #1536 from WebFuzzing/fix/coverage
Fix IndexOutOfBoundsException when actionResults is shorter than additionalInfoList
2 parents c9b32a9 + 54bff3f commit affbbc2

2 files changed

Lines changed: 108 additions & 1 deletion

File tree

core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ abstract class AbstractRestFitness : HttpWsFitness<RestIndividual>() {
211211
return
212212
}
213213

214+
/*
215+
actionResults contains only the REST calls that were executed on the client side,
216+
collected one by one during the test run. If execution was stopped early (e.g.,
217+
due to a stopping condition or timeout), fewer results are recorded here than
218+
the number of additional info entries the SUT reports back. This guard avoids
219+
an index-out-of-bounds when iterating below.
220+
*/
221+
if (actionResults.size < additionalInfoList.size) {
222+
log.warn("Length mismatch between ${actionResults.size} action results and ${additionalInfoList.size} info data")
223+
return
224+
}
225+
214226
for (i in additionalInfoList.indices) {
215227

216228
val action = individual.seeAllActions()[i]
@@ -389,7 +401,9 @@ abstract class AbstractRestFitness : HttpWsFitness<RestIndividual>() {
389401
handleAdvancedBlackBoxCriteria(fv, actions[it], result)
390402
handleHttpStatusOracles(fv, actions[it], result, it)
391403

392-
val location5xx: String? = getlocation5xx(status, additionalInfoList, it, result, name)
404+
val location5xx: String? = if (it < additionalInfoList.size)
405+
getlocation5xx(status, additionalInfoList, it, result, name)
406+
else null
393407
handleAdditionalStatusTargetDescription(result, fv, status, name, it, location5xx)
394408
handleAuthTargets(status, actions, it, name, fv)
395409
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.evomaster.core.problem.rest.service.fitness
2+
3+
import io.mockk.every
4+
import io.mockk.mockk
5+
import org.evomaster.client.java.controller.api.dto.AdditionalInfoDto
6+
import org.evomaster.core.EMConfig
7+
import org.evomaster.core.TestUtils
8+
import org.evomaster.core.problem.enterprise.SampleType
9+
import org.evomaster.core.problem.rest.data.RestCallResult
10+
import org.evomaster.core.problem.rest.data.RestIndividual
11+
import org.evomaster.core.problem.rest.resource.RestResourceCalls
12+
import org.evomaster.core.search.FitnessValue
13+
import org.evomaster.core.search.service.FitnessFunction
14+
import org.evomaster.core.search.service.IdMapper
15+
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
16+
import org.junit.jupiter.api.Test
17+
18+
internal class AbstractRestFitnessTest {
19+
20+
private fun makeIndividual(numActions: Int): RestIndividual {
21+
val calls = (1..numActions).map { i ->
22+
RestResourceCalls(
23+
actions = listOf(TestUtils.generateFakeQueryRestAction("$i", "/path$i")),
24+
sqlActions = emptyList()
25+
)
26+
}.toMutableList()
27+
val ind = RestIndividual(calls, SampleType.RANDOM)
28+
ind.resetLocalIdRecursively()
29+
ind.doInitializeLocalId()
30+
ind.doInitialize()
31+
return ind
32+
}
33+
34+
private fun injectField(target: Any, declaringClass: Class<*>, fieldName: String, value: Any) {
35+
val field = declaringClass.getDeclaredField(fieldName)
36+
field.isAccessible = true
37+
field.set(target, value)
38+
}
39+
40+
/**
41+
* Regression test for crash: Index 1 out of bounds for length 1 in expandIndividual
42+
* when actionResults is shorter than additionalInfoList.
43+
*/
44+
@Test
45+
fun `expandIndividual does not crash when actionResults is shorter than additionalInfoList`() {
46+
val fitness = mockk<AbstractRestFitness>(relaxed = true)
47+
every { fitness.expandIndividual(any(), any(), any()) } answers { callOriginal() }
48+
49+
val individual = makeIndividual(2)
50+
val result1 = RestCallResult("r1", false)
51+
52+
assertDoesNotThrow {
53+
fitness.expandIndividual(
54+
individual,
55+
additionalInfoList = listOf(AdditionalInfoDto(), AdditionalInfoDto()),
56+
actionResults = listOf(result1)
57+
)
58+
}
59+
}
60+
61+
/**
62+
* Regression test for crash: Index 1 out of bounds for length 1 in getlocation5xx
63+
* when handleResponseTargets iterates over actionResults but additionalInfoList is shorter.
64+
*/
65+
@Test
66+
fun `handleResponseTargets does not crash when actionResults is longer than additionalInfoList with 500 status`() {
67+
val fitness = mockk<AbstractRestFitness>(relaxed = true)
68+
every { fitness.handleResponseTargets(any(), any(), any(), any()) } answers { callOriginal() }
69+
70+
val idMapper = mockk<IdMapper>(relaxed = true)
71+
val config = EMConfig().also {
72+
it.security = false
73+
it.advancedBlackBoxCoverage = false
74+
}
75+
injectField(fitness, FitnessFunction::class.java, "idMapper", idMapper)
76+
injectField(fitness, FitnessFunction::class.java, "config", config)
77+
78+
val action1 = TestUtils.generateFakeQueryRestAction("1", "/foo")
79+
val action2 = TestUtils.generateFakeQueryRestAction("2", "/bar")
80+
81+
val result1 = RestCallResult("r1", false).also { it.setStatusCode(500) }
82+
val result2 = RestCallResult("r2", false).also { it.setStatusCode(500) }
83+
84+
assertDoesNotThrow {
85+
fitness.handleResponseTargets(
86+
fv = FitnessValue(2.0),
87+
actions = listOf(action1, action2),
88+
actionResults = listOf(result1, result2),
89+
additionalInfoList = listOf(AdditionalInfoDto())
90+
)
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)