Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.webfuzzing.asyncapi.models.AsyncApiReply
import org.evomaster.client.java.controller.api.dto.SutInfoDto
import org.evomaster.client.java.controller.api.dto.problem.asyncapi.AsyncApiActionDto
import org.evomaster.client.java.controller.api.dto.problem.asyncapi.AsyncApiReplyDto
import org.evomaster.core.Lazy
import org.evomaster.core.database.sql.SqlAction
import org.evomaster.core.logging.LoggingUtil
import org.evomaster.core.problem.api.service.ApiWsFitness
Expand All @@ -24,6 +25,7 @@ import org.evomaster.core.search.EvaluatedIndividual
import org.evomaster.core.search.FitnessValue
import org.evomaster.core.search.action.ActionResult
import org.evomaster.core.search.gene.utils.GeneUtils
import org.evomaster.core.taint.TaintAnalysis
import org.slf4j.Logger
import org.slf4j.LoggerFactory

Expand Down Expand Up @@ -160,6 +162,21 @@ class AsyncApiFitness : ApiWsFitness<AsyncApiIndividual>() {
val dto = updateFitnessAfterEvaluation(targets, allTargets, fullyCovered, descriptiveIds, individual, fv)
?: return null
handleExtra(dto, fv)

if (epc.isInSearch() && config.isEnabledTaintAnalysis()) {
/*
A tainted string in a payload or a header reaches the service inside the
message. The analysis matches what the service reports against the genes of
every action, so nested fields are covered, whichever thread consumed it.

At most one entry per result, not exactly one as GraphQL asserts: a driver that
could not be reached records no entry for its message.

Only during the search, as for REST and GraphQL: nothing mutates the genes later.
*/
Lazy.assert { dto.additionalInfoList.size <= actionResults.count { it is AsyncApiCallResult } }
TaintAnalysis.doTaintAnalysis(individual, dto.additionalInfoList, randomness, config)
}
}

return EvaluatedIndividual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import com.google.inject.Injector
import com.google.inject.Key
import com.google.inject.TypeLiteral
import com.webfuzzing.asyncapi.access.AsyncApiAccess
import org.evomaster.client.java.controller.api.dto.AdditionalInfoDto
import org.evomaster.client.java.controller.api.dto.StringSpecializationInfoDto
import org.evomaster.client.java.controller.api.dto.TargetInfoDto
import org.evomaster.client.java.controller.api.dto.TestResultsDto
import org.evomaster.client.java.controller.api.dto.problem.asyncapi.AsyncApiActionDto
import org.evomaster.client.java.controller.api.dto.problem.asyncapi.AsyncApiReplyDto
import org.evomaster.client.java.instrumentation.shared.ObjectiveNaming
import org.evomaster.client.java.instrumentation.shared.StringSpecialization
import org.evomaster.client.java.instrumentation.shared.TaintInputName
import org.evomaster.client.java.instrumentation.shared.TaintType
import org.evomaster.core.EMConfig
import org.evomaster.core.problem.asyncapi.data.AsyncApiAction
import org.evomaster.core.problem.asyncapi.data.AsyncApiCallResult
import org.evomaster.core.problem.asyncapi.data.AsyncApiIndividual
Expand All @@ -21,10 +27,12 @@ import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory
import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.problem.rest.builder.RestActionBuilderV3
import org.evomaster.core.search.EvaluatedIndividual
import org.evomaster.core.search.gene.string.StringGene
import org.evomaster.core.search.service.FitnessFunction
import org.evomaster.core.search.service.IdMapper
import org.evomaster.core.search.service.Randomness
import org.evomaster.core.search.service.SearchGlobalState
import org.evomaster.core.search.service.time.ExecutionPhaseController
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -56,6 +64,21 @@ class AsyncApiFitnessTest {
*/
private const val SUT_LINE_ID = 1

/**
* The payload field of [EVENTS] that taint analysis is checked on.
*/
private const val PAYLOAD_FIELD = "id"

/**
* What the service compared the tainted payload field with, in the tests on taint.
*/
private const val COMPARED_WITH = "order-created"

/**
* A value taint analysis would recognise as its own once the service reports it back.
*/
private val TAINTED = TaintInputName.getTaintName(0)

/**
* One operation that expects nothing back, and one whose message carries a header
* besides the one the correlation id is stamped into.
Expand Down Expand Up @@ -242,6 +265,106 @@ class AsyncApiFitnessTest {
assertEquals(2, driver.published.size)
}

/**
* The [PAYLOAD_FIELD] string gene in the payload of the action at [index].
*/
private fun payloadField(individual: AsyncApiIndividual, index: Int = 0): StringGene =
individual.seeMainExecutableActions()[index].seeTopGenes()
.flatMap { it.flatView() }
.filterIsInstance<StringGene>()
.single { it.name == PAYLOAD_FIELD }

/**
* What an instrumented driver reports for an action where the service compared [TAINTED]
* with [COMPARED_WITH].
*/
private fun comparedWithAConstant() = AdditionalInfoDto().apply {
stringSpecializations[TAINTED] = listOf(StringSpecializationInfoDto(
StringSpecialization.CONSTANT.name, COMPARED_WITH, TaintType.FULL_MATCH.name))
}

/**
* Evaluates [count] messages of [EVENTS], the first of which carries [TAINTED] in its payload,
* with the driver reporting [perAction] as what the service did with its inputs.
*/
private fun evaluateTainted(count: Int, vararg perAction: AdditionalInfoDto): EvaluatedIndividual<AsyncApiIndividual> {

driver.testResults = { TestResultsDto().apply { additionalInfoList.addAll(perAction) } }

val individual = individualOf(*Array(count) { "publishEvent" })
payloadField(individual).value = TAINTED

return fitness.calculateCoverage(individual, modifiedSpec = null)
?: fail("the fitness gave up on the individual")
}

private fun startTheSearch() =
injector.getInstance(ExecutionPhaseController::class.java).markStartingSearch()

private fun specializationsOf(gene: StringGene) = gene.specializationGenes.map { it.getValueAsRawString() }

@Test
fun testAPayloadStringLearnsWhatTheServiceComparedItWith() {

start(EVENTS) { fireAndForget() }
startTheSearch()

val evaluated = evaluateTainted(1, comparedWithAConstant())

//the tainted value went out inside the message, which is how the service got to see it
assertTrue(driver.published.single().payload.contains(TAINTED), driver.published.single().payload)
assertEquals(listOf(COMPARED_WITH), specializationsOf(payloadField(evaluated.individual)))
}

@Test
fun testATestThatStoppedAtAFailedPublishIsStillAnalysed() {

/*
The second publish fails, so the controller reports two entries for three actions.
That is what a test that stops early looks like, not an inconsistency.
*/
var sent = 0
start(EVENTS) {
sent++
if (sent == 1) fireAndForget() else AsyncApiReplyDto().apply { published = false }
}
startTheSearch()

val evaluated = evaluateTainted(3, comparedWithAConstant(), AdditionalInfoDto())

assertEquals(2, driver.published.size)
assertTrue(results(evaluated)[1].stopping)
assertEquals(listOf(COMPARED_WITH), specializationsOf(payloadField(evaluated.individual)))
}

@Test
fun testABlackBoxRunNeverRunsTaintAnalysis() {

/*
MIO turns taint analysis on even in black-box mode, but there is nothing to analyse
without asking the driver what the service did, and a black-box run does not ask.
*/
start(EVENTS, "$BLACK_BOX=true", "--algorithm=MIO") { fireAndForget() }
startTheSearch()
assertTrue(injector.getInstance(EMConfig::class.java).isEnabledTaintAnalysis())

val evaluated = evaluateTainted(1, comparedWithAConstant())

assertEquals(0, driver.testResultsRequests)
assertTrue(specializationsOf(payloadField(evaluated.individual)).isEmpty())
}

@Test
fun testTaintAnalysisRunsOnlyDuringTheSearch() {

start(EVENTS) { fireAndForget() }

val evaluated = evaluateTainted(1, comparedWithAConstant())

assertTrue(driver.testResultsRequests > 0)
assertTrue(specializationsOf(payloadField(evaluated.individual)).isEmpty())
}

@Test
fun testTheDriverIsToldEverythingItNeeds() {

Expand Down
Loading