Skip to content

Commit f2ae275

Browse files
authored
Merge branch 'master' into ssrf-test-assertions
2 parents 8a727f2 + 5bbad0a commit f2ae275

9 files changed

Lines changed: 508 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ Examples of Fortune 500 companies using _EvoMaster_ are:
198198

199199
![](docs/img/video-player-flaticon.png)
200200

201+
* A [45-minute talk given at TestCon'25](https://www.youtube.com/watch?v=uKKRo3LrNiw&list=PLqYhGsQ9iSEoXaRmW9WQjjXJK_1NbLlZ6&index=15) on Fuzz Testing Web APIs gives an overview of what can be expected from this kind of fuzzers.
202+
201203
* A [short video](https://youtu.be/3mYxjgnhLEo) (5 minutes)
202204
shows the use of _EvoMaster_ on one of the
203205
case studies in [EMB](https://github.com/WebFuzzing/EMB).

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,7 @@ class EMConfig {
11621162

11631163
enum class Algorithm {
11641164
DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW,
1165-
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA, MuPlusLambdaEA // GA variants still work-in-progress.
1165+
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA, MuPlusLambdaEA, LIPS // GA variants still work-in-progress.
11661166
}
11671167

11681168
@Cfg("The algorithm used to generate test cases. The default depends on whether black-box or white-box testing is done.")

core/src/main/kotlin/org/evomaster/core/Main.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,8 @@ class Main {
645645
EMConfig.Algorithm.StandardGA ->
646646
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<GraphQLIndividual>>() {})
647647

648+
EMConfig.Algorithm.LIPS ->
649+
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.LIPSAlgorithm<GraphQLIndividual>>() {})
648650
EMConfig.Algorithm.MuPlusLambdaEA ->
649651
Key.get(object : TypeLiteral<MuPlusLambdaEvolutionaryAlgorithm<GraphQLIndividual>>() {})
650652

@@ -684,6 +686,8 @@ class Main {
684686

685687
EMConfig.Algorithm.RW ->
686688
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RPCIndividual>>() {})
689+
EMConfig.Algorithm.LIPS ->
690+
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.LIPSAlgorithm<RPCIndividual>>() {})
687691

688692
EMConfig.Algorithm.MuPlusLambdaEA ->
689693
Key.get(object : TypeLiteral<MuPlusLambdaEvolutionaryAlgorithm<RPCIndividual>>() {})
@@ -722,6 +726,8 @@ class Main {
722726

723727
EMConfig.Algorithm.RW ->
724728
Key.get(object : TypeLiteral<RandomWalkAlgorithm<WebIndividual>>() {})
729+
EMConfig.Algorithm.LIPS ->
730+
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.LIPSAlgorithm<WebIndividual>>() {})
725731

726732
EMConfig.Algorithm.MuPlusLambdaEA ->
727733
Key.get(object : TypeLiteral<MuPlusLambdaEvolutionaryAlgorithm<WebIndividual>>() {})
@@ -769,6 +775,8 @@ class Main {
769775

770776
EMConfig.Algorithm.RW ->
771777
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RestIndividual>>() {})
778+
EMConfig.Algorithm.LIPS ->
779+
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.LIPSAlgorithm<RestIndividual>>() {})
772780
EMConfig.Algorithm.MuPlusLambdaEA ->
773781
Key.get(object : TypeLiteral<MuPlusLambdaEvolutionaryAlgorithm<RestIndividual>>() {})
774782
EMConfig.Algorithm.MuLambdaEA ->
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package org.evomaster.core.search.algorithms
2+
3+
import com.google.inject.Inject
4+
import org.evomaster.core.EMConfig
5+
import org.evomaster.core.search.Individual
6+
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
7+
import org.evomaster.client.java.instrumentation.shared.ObjectiveNaming
8+
import org.evomaster.core.search.service.IdMapper
9+
10+
/**
11+
* Linearly Independent Path-based Search (LIPS).
12+
*
13+
* A single-objective GA that optimizes one branch target at a time.
14+
*
15+
* - Initializes a random individual i and build the initial population P = random ∪ {i}.
16+
* - Maintains a current branch target.
17+
* - Per-target budget is a fair share of the global TIME/ACTIONS budget; switches target when the target is covered or its budget is exhausted.
18+
*/
19+
class LIPSAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
20+
21+
@Inject
22+
private lateinit var idMapper: IdMapper
23+
24+
private var currentTarget: Int? = null
25+
private lateinit var budget: LipsBudget
26+
27+
override fun getType(): EMConfig.Algorithm = EMConfig.Algorithm.LIPS
28+
29+
override fun initPopulation() {
30+
population.clear()
31+
// 1) Generate Random Individual
32+
val i = sampleSuite()
33+
// 2) P <- RandomPopulation(ps-1) ∪ {i}
34+
population.add(i)
35+
while (population.size < config.populationSize) {
36+
population.add(sampleSuite())
37+
}
38+
// Initialize budget manager and first per-target budget using current uncovered targets
39+
budget = LipsBudget(config, time)
40+
val initUncoveredSize = archive.notCoveredTargets().size
41+
budget.budgetLeftForCurrentTarget = budget.computePerTargetBudget(initUncoveredSize)
42+
}
43+
44+
override fun searchOnce() {
45+
beginGeneration()
46+
// record budget usage for this generation
47+
val startActions = time.evaluatedActions
48+
val startSeconds = time.getElapsedSeconds()
49+
50+
// Compute uncovered goals
51+
val uncovered = archive.notCoveredTargets()
52+
53+
// current target is null if covered by previous generation or out of budget
54+
// Pick target if null, or if previously covered (check coverage directly)
55+
val needNewTarget = currentTarget == null || archive.isCovered(currentTarget!!)
56+
if (needNewTarget) {
57+
val target = lastUncoveredBranchTargetId()
58+
currentTarget = target
59+
// Initialize budget for this NEW target
60+
budget.budgetLeftForCurrentTarget = budget.computePerTargetBudget(uncovered.size)
61+
}
62+
63+
// Focus scoring on the single selected target if present; otherwise use global fitness
64+
if (currentTarget == null) {
65+
frozenTargets = emptySet()
66+
} else {
67+
frozenTargets = setOf(currentTarget!!)
68+
}
69+
70+
val n = config.populationSize
71+
val nextPop: MutableList<WtsEvalIndividual<T>> = formTheNextPopulation(population)
72+
73+
while (nextPop.size < n) {
74+
beginStep()
75+
76+
val p1 = tournamentSelection()
77+
val p2 = tournamentSelection()
78+
79+
val o1 = p1.copy()
80+
val o2 = p2.copy()
81+
82+
if (randomness.nextBoolean(config.xoverProbability)) {
83+
xover(o1, o2)
84+
}
85+
if (randomness.nextBoolean(config.fixedRateMutation)) {
86+
mutate(o1)
87+
}
88+
if (randomness.nextBoolean(config.fixedRateMutation)) {
89+
mutate(o2)
90+
}
91+
92+
nextPop.add(o1)
93+
nextPop.add(o2)
94+
95+
// Stop if global budget or target budget is up
96+
val usedForTarget = budget.usedForCurrentTarget(startActions, startSeconds)
97+
if (!time.shouldContinueSearch() || usedForTarget >= budget.budgetLeftForCurrentTarget) {
98+
endStep()
99+
break
100+
}
101+
endStep()
102+
}
103+
104+
population.clear()
105+
population.addAll(nextPop)
106+
107+
// Update budget usage for this target
108+
budget.updatePerTargetBudget(startActions, startSeconds)
109+
110+
// Switch target if covered or out of budget
111+
val coveredNow = population.any { score(it) >= 1.0 }
112+
val switching = budget.shouldSwitchTarget(coveredNow)
113+
if (switching) currentTarget = null
114+
115+
endGeneration()
116+
}
117+
118+
fun lastUncoveredBranchTargetId(): Int? {
119+
val snapshot = archive.getSnapshotOfBestIndividuals()
120+
if (snapshot.isEmpty()) return null
121+
122+
// Iterate targets by numeric id in descending order
123+
val orderedIds = snapshot.keys.sortedDescending()
124+
125+
for (targetId in orderedIds) {
126+
val description = idMapper.getDescriptiveId(targetId)
127+
val isBranch = description.startsWith(ObjectiveNaming.BRANCH)
128+
val covered = archive.isCovered(targetId)
129+
if (isBranch) {
130+
if (!covered) {
131+
return targetId
132+
}
133+
}
134+
}
135+
return null
136+
}
137+
}
138+
139+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.evomaster.core.search.algorithms
2+
3+
import org.evomaster.core.EMConfig
4+
import org.evomaster.core.search.service.SearchTimeController
5+
6+
/**
7+
* Encapsulates per-target budget accounting for LIPS.
8+
* It derives fair-share budgets from the global stopping criterion
9+
* and tracks the remaining budget for the current target.
10+
*/
11+
class LipsBudget(
12+
private val config: EMConfig,
13+
private val time: SearchTimeController
14+
) {
15+
16+
var budgetLeftForCurrentTarget: Int = 0
17+
18+
fun computePerTargetBudget(uncoveredSize: Int): Int {
19+
return when (config.stoppingCriterion) {
20+
EMConfig.StoppingCriterion.ACTION_EVALUATIONS -> {
21+
val remaining = (config.maxEvaluations - time.evaluatedActions).coerceAtLeast(0)
22+
if (uncoveredSize <= 0) remaining else remaining / uncoveredSize
23+
}
24+
EMConfig.StoppingCriterion.TIME -> {
25+
val remaining = (config.timeLimitInSeconds() - time.getElapsedSeconds()).coerceAtLeast(0)
26+
if (uncoveredSize <= 0) remaining else remaining / uncoveredSize
27+
}
28+
else -> Int.MAX_VALUE
29+
}
30+
}
31+
32+
fun usedForCurrentTarget(startActions: Int, startSeconds: Int): Int {
33+
return when (config.stoppingCriterion) {
34+
EMConfig.StoppingCriterion.ACTION_EVALUATIONS -> time.evaluatedActions - startActions
35+
EMConfig.StoppingCriterion.TIME -> time.getElapsedSeconds() - startSeconds
36+
else -> 0
37+
}
38+
}
39+
40+
fun updatePerTargetBudget(actionsAtGenStart: Int, secondsAtGenStart: Int) {
41+
val used = usedForCurrentTarget(actionsAtGenStart, secondsAtGenStart)
42+
budgetLeftForCurrentTarget -= used
43+
}
44+
45+
fun shouldSwitchTarget(coveredNow: Boolean): Boolean {
46+
val outOfBudget = budgetLeftForCurrentTarget <= 0
47+
return coveredNow || outOfBudget
48+
}
49+
}
50+
51+

core/src/main/kotlin/org/evomaster/core/search/service/Archive.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,5 +700,4 @@ class Archive<T> where T : Individual {
700700
it.individual.seeTopGenes().all { g-> g.isLocallyValid() }
701701
}
702702
}
703-
704703
}

0 commit comments

Comments
 (0)