From 2ebfa6ceac48751cfadcb872f0bb4537a728fde0 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Fri, 18 Sep 2026 01:52:23 -0300 Subject: [PATCH 1/2] Avoid NPE in TaintedArrayGene --- .../gene/collection/TaintedArrayGene.kt | 8 ++ .../gene/collection/TaintedArrayGeneTest.kt | 76 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt index 65dc2f4efb..94b99a5a2d 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt @@ -129,6 +129,14 @@ class TaintedArrayGene( return false } + /* + Resolving a taint adds the array as a child, so a resolved and an unresolved gene do not + have the same structure, and copying a value cannot turn one into the other + */ + if(this.isResolved() != other.isResolved()){ + return false + } + return this.arrayGene?.unsafeCopyValueFrom(other.arrayGene!!) ?: true } diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt new file mode 100644 index 0000000000..5ba536f3bb --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt @@ -0,0 +1,76 @@ +package org.evomaster.core.search.gene.collection + +import org.evomaster.client.java.instrumentation.shared.TaintInputName +import org.evomaster.core.search.gene.Gene +import org.evomaster.core.search.gene.numeric.IntegerGene +import org.evomaster.core.search.gene.wrapper.ChoiceGene +import org.evomaster.core.search.service.Randomness +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class TaintedArrayGeneTest { + + private val randomness = Randomness() + + private fun resolved(): TaintedArrayGene = + TaintedArrayGene("resolved", TaintInputName.getTaintName(1), true, ArrayGene("array", IntegerGene("element"))) + + private fun unresolved(): TaintedArrayGene = + TaintedArrayGene("unresolved", TaintInputName.getTaintName(2)) + + @Test + fun testCopyBetweenResolvedGenes() { + val target = resolved().apply { doInitialize(randomness) } + val source = resolved().apply { doInitialize(randomness) } + source.randomize(randomness, true) + + assertTrue(target.copyValueFrom(source)) + assertTrue(target.containsSameValueAs(source)) + } + + /** + * This used to throw a NullPointerException, as the missing array of the source was dereferenced. + */ + @Test + fun testCopyFromUnresolvedIntoResolvedFails() { + val target = resolved().apply { doInitialize(randomness) } + val source = unresolved().apply { doInitialize(randomness) } + val before = target.copy() + + assertFalse(target.copyValueFrom(source)) + assertTrue(target.isResolved()) + assertTrue(target.containsSameValueAs(before)) + } + + /** + * This used to report a successful copy, even if the target was left without the array of the source. + */ + @Test + fun testCopyFromResolvedIntoUnresolvedFails() { + val target = unresolved().apply { doInitialize(randomness) } + val source = resolved().apply { doInitialize(randomness) } + + assertFalse(target.copyValueFrom(source)) + assertFalse(target.isResolved()) + assertFalse(target.containsSameValueAs(source)) + } + + /** + * A choice has to copy into the alternative in the same state as the source, and not into the + * first alternative of the same class. This used to throw a NullPointerException. + */ + @Test + fun testChoiceCopiesIntoTheAlternativeInTheSameState() { + val choice = ChoiceGene("choice", listOf(resolved(), unresolved())) + choice.doInitialize(randomness) + + val target = choice.copy() as ChoiceGene<*> + target.selectActiveGene(0) + val source = choice.copy() as ChoiceGene<*> + source.selectActiveGene(1) + + assertTrue(target.copyValueFrom(source)) + assertEquals(1, target.activeGeneIndex) + assertTrue(target.containsSameValueAs(source)) + } +} \ No newline at end of file From d5b4274ddeef730767f45c36bc1075783a4a1c10 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Tue, 22 Sep 2026 01:52:49 -0300 Subject: [PATCH 2/2] Handle cases with unresolved array genes --- .../gene/collection/TaintedArrayGene.kt | 18 +++++--- .../gene/collection/TaintedArrayGeneTest.kt | 45 ++++++++++++------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt index 94b99a5a2d..e17de33d3d 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGene.kt @@ -129,16 +129,20 @@ class TaintedArrayGene( return false } - /* - Resolving a taint adds the array as a child, so a resolved and an unresolved gene do not - have the same structure, and copying a value cannot turn one into the other - */ - if(this.isResolved() != other.isResolved()){ + val otherArray = other.arrayGene + if(otherArray == null){ + // other is unresolved: drop the array, if any + killAllChildren() + } else if(arrayGene == null){ + // other is resolved but this is not: resolve this with a copy of its array + addChild(otherArray.copy()) + } else if(!arrayGene!!.unsafeCopyValueFrom(otherArray)){ return false } - return this.arrayGene?.unsafeCopyValueFrom(other.arrayGene!!) - ?: true + taintedValue = other.taintedValue + isActive = other.isActive + return true } override fun getPossiblyTaintedValue(): String { diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt index 5ba536f3bb..85cf15da64 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/collection/TaintedArrayGeneTest.kt @@ -12,11 +12,11 @@ class TaintedArrayGeneTest { private val randomness = Randomness() - private fun resolved(): TaintedArrayGene = - TaintedArrayGene("resolved", TaintInputName.getTaintName(1), true, ArrayGene("array", IntegerGene("element"))) + private fun resolved(taintId: Int = 1): TaintedArrayGene = + TaintedArrayGene("resolved", TaintInputName.getTaintName(taintId), true, ArrayGene("array", IntegerGene("element"))) - private fun unresolved(): TaintedArrayGene = - TaintedArrayGene("unresolved", TaintInputName.getTaintName(2)) + private fun unresolved(taintId: Int = 2): TaintedArrayGene = + TaintedArrayGene("unresolved", TaintInputName.getTaintName(taintId)) @Test fun testCopyBetweenResolvedGenes() { @@ -28,39 +28,51 @@ class TaintedArrayGeneTest { assertTrue(target.containsSameValueAs(source)) } + /** + * This used to report a successful copy, even if the tainted value of the source was not copied. + */ + @Test + fun testCopyBetweenUnresolvedGenes() { + val target = unresolved(2).apply { doInitialize(randomness) } + val source = unresolved(3).apply { doInitialize(randomness) } + + assertTrue(target.copyValueFrom(source)) + assertEquals(source.taintedValue, target.taintedValue) + assertTrue(target.containsSameValueAs(source)) + } + /** * This used to throw a NullPointerException, as the missing array of the source was dereferenced. */ @Test - fun testCopyFromUnresolvedIntoResolvedFails() { + fun testCopyFromUnresolvedIntoResolved() { val target = resolved().apply { doInitialize(randomness) } val source = unresolved().apply { doInitialize(randomness) } - val before = target.copy() - assertFalse(target.copyValueFrom(source)) - assertTrue(target.isResolved()) - assertTrue(target.containsSameValueAs(before)) + assertTrue(target.copyValueFrom(source)) + assertFalse(target.isResolved()) + assertTrue(target.containsSameValueAs(source)) } /** * This used to report a successful copy, even if the target was left without the array of the source. */ @Test - fun testCopyFromResolvedIntoUnresolvedFails() { + fun testCopyFromResolvedIntoUnresolved() { val target = unresolved().apply { doInitialize(randomness) } val source = resolved().apply { doInitialize(randomness) } - assertFalse(target.copyValueFrom(source)) - assertFalse(target.isResolved()) - assertFalse(target.containsSameValueAs(source)) + assertTrue(target.copyValueFrom(source)) + assertTrue(target.isResolved()) + assertTrue(target.containsSameValueAs(source)) } /** - * A choice has to copy into the alternative in the same state as the source, and not into the - * first alternative of the same class. This used to throw a NullPointerException. + * A choice copies into its first alternative of the same class as the source, which here is in a + * different state than the source. This used to throw a NullPointerException. */ @Test - fun testChoiceCopiesIntoTheAlternativeInTheSameState() { + fun testChoiceCopiesIntoAnAlternativeInADifferentState() { val choice = ChoiceGene("choice", listOf(resolved(), unresolved())) choice.doInitialize(randomness) @@ -70,7 +82,6 @@ class TaintedArrayGeneTest { source.selectActiveGene(1) assertTrue(target.copyValueFrom(source)) - assertEquals(1, target.activeGeneIndex) assertTrue(target.containsSameValueAs(source)) } } \ No newline at end of file