From 97495833d4ef43a6030aa34947ce2409f806c350 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Sat, 16 May 2026 13:16:04 +0200 Subject: [PATCH] test(analyzer): Add lambda-captured-array alias regression sample Wires the standalone reproducer for the IFDS lambda-captured-array alias bug into the analyzer test system. `sinkInsideLambda` passes; the bug case `capturedArrayOuterRead` fails today (false negative) and will start passing when the fact-propagation gap is fixed. --- .../LambdaCapturedArrayAliasSample.java | 23 +++++++++++++ .../dataflow/JavaDataFlowReachabilityTest.kt | 34 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 core/samples/src/main/java/test/samples/LambdaCapturedArrayAliasSample.java diff --git a/core/samples/src/main/java/test/samples/LambdaCapturedArrayAliasSample.java b/core/samples/src/main/java/test/samples/LambdaCapturedArrayAliasSample.java new file mode 100644 index 000000000..d1af2c0f1 --- /dev/null +++ b/core/samples/src/main/java/test/samples/LambdaCapturedArrayAliasSample.java @@ -0,0 +1,23 @@ +package test.samples; + +import java.util.function.Consumer; + +public class LambdaCapturedArrayAliasSample { + + public String source() { return "tainted"; } + public String sink(String value) { return value; } + + private void emit(Consumer consumer) { + consumer.accept(source()); + } + + public void sinkInsideLambda() { + emit(line -> sink(line)); + } + + public void capturedArrayOuterRead() { + String[] holder = new String[1]; + emit(line -> holder[0] = line); + sink(holder[0]); + } +} diff --git a/core/src/test/kotlin/org/opentaint/jvm/sast/dataflow/JavaDataFlowReachabilityTest.kt b/core/src/test/kotlin/org/opentaint/jvm/sast/dataflow/JavaDataFlowReachabilityTest.kt index 611b8c516..19a6c1162 100644 --- a/core/src/test/kotlin/org/opentaint/jvm/sast/dataflow/JavaDataFlowReachabilityTest.kt +++ b/core/src/test/kotlin/org/opentaint/jvm/sast/dataflow/JavaDataFlowReachabilityTest.kt @@ -18,6 +18,7 @@ class JavaDataFlowReachabilityTest : AnalysisTest() { private const val OPTIONAL_RULE_ID = "optional-flow-rule" private const val STREAM_RULE_ID = "stream-flow-rule" private const val ASYNC_RULE_ID = "async-flow-rule" + private const val CAPTURED_ARRAY_RULE_ID = "captured-array-alias-rule" } override val sourceFileExtension: String = "java" @@ -481,6 +482,34 @@ class JavaDataFlowReachabilityTest : AnalysisTest() { ) } + @Test + fun `lambda capture - sink invoked inside lambda body`() { + val testCls = "$SAMPLE_PACKAGE.LambdaCapturedArrayAliasSample" + val config = capturedArrayConfig(testCls) + + assertReachable( + config = config, + testCls = testCls, + entryPointName = "sinkInsideLambda", + ruleId = CAPTURED_ARRAY_RULE_ID, + testName = "lambda capture sink inside lambda" + ) + } + + @Test + fun `lambda capture - tainted value written to captured array, read in outer scope`() { + val testCls = "$SAMPLE_PACKAGE.LambdaCapturedArrayAliasSample" + val config = capturedArrayConfig(testCls) + + assertReachable( + config = config, + testCls = testCls, + entryPointName = "capturedArrayOuterRead", + ruleId = CAPTURED_ARRAY_RULE_ID, + testName = "lambda captured array outer read" + ) + } + private fun collectionConfig(testCls: String) = SerializedTaintConfig( source = listOf(sourceRule(testCls, "source", TAINT_MARK)), sink = listOf(sinkRule(testCls, "sink", COLLECTION_RULE_ID, listOf(Argument(0) to TAINT_MARK))) @@ -510,4 +539,9 @@ class JavaDataFlowReachabilityTest : AnalysisTest() { source = listOf(sourceRule(testCls, "source", TAINT_MARK)), sink = listOf(sinkRule(testCls, "sink", ASYNC_RULE_ID, listOf(Argument(0) to TAINT_MARK))) ) + + private fun capturedArrayConfig(testCls: String) = SerializedTaintConfig( + source = listOf(sourceRule(testCls, "source", TAINT_MARK)), + sink = listOf(sinkRule(testCls, "sink", CAPTURED_ARRAY_RULE_ID, listOf(Argument(0) to TAINT_MARK))) + ) }