diff --git a/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java b/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java index 3d7c84915..c76ab1e17 100644 --- a/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java +++ b/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java @@ -40,10 +40,12 @@ import com.ibm.engine.rule.Parameter; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -691,7 +693,7 @@ public boolean isInvocationOnVariable( Symbol symbol = symbolOptional.get(); if (symbol.isVariableSymbol()) { - return symbol.name().equals(variable.name()); + return areSymbolsEquivalent(symbol, variable); } return true; } @@ -713,13 +715,44 @@ public boolean isInitForVariable(Tree newClass, @Nonnull TraceSymbol var Symbol symbol = symbolOptional.get(); if (symbol.isVariableSymbol()) { - return symbol.name().equals(variable.name()); + return areSymbolsEquivalent(symbol, variable); } return true; } return false; } + private boolean areSymbolsEquivalent(@Nonnull Symbol s1, @Nonnull Symbol s2) { + if (s1.equals(s2)) { + return true; + } + + Symbol t1 = traceSymbol(s1); + Symbol t2 = traceSymbol(s2); + + return t1.equals(t2); + } + + @Nonnull + private Symbol traceSymbol(@Nonnull Symbol symbol) { + return traceSymbol(symbol, new HashSet<>()); + } + + @Nonnull + private Symbol traceSymbol(@Nonnull Symbol symbol, @Nonnull Set visited) { + if (!visited.add(symbol)) { + return symbol; + } + Tree declaration = symbol.declaration(); + if (declaration instanceof VariableTree variableTree) { + ExpressionTree initializer = variableTree.initializer(); + if (initializer instanceof IdentifierTree identifierTree) { + return traceSymbol(identifierTree.symbol(), visited); + } + } + return symbol; + } + @Nonnull private Optional> getTraceSymbol( @Nonnull Parameter parameter, @Nonnull Arguments arguments) { @@ -922,17 +955,25 @@ private Optional>> getIdentifier @Nonnull private IdentifierTree traceVariable(@Nonnull IdentifierTree identifierTree) { Tree declaration = identifierTree.symbol().declaration(); - if (declaration == null) { + if (declaration == null || !(declaration instanceof VariableTree variableTree)) { return identifierTree; } - - if (declaration instanceof VariableTree variableTree1) { - ExpressionTree initTree = variableTree1.initializer(); - if (initTree instanceof IdentifierTree identifierTree1) { - return traceVariable(identifierTree1); - } + ExpressionTree initTree = variableTree.initializer(); + if (!(initTree instanceof IdentifierTree nextId)) { + return identifierTree; } - return identifierTree; + // Delegate chain-following to traceSymbol (single recursive walker) + Symbol finalSymbol = traceSymbol(nextId.symbol()); + // Walk the identifier chain from nextId until we reach the identifier for finalSymbol + IdentifierTree current = nextId; + while (!current.symbol().equals(finalSymbol)) { + Tree decl = current.symbol().declaration(); + if (!(decl instanceof VariableTree vt)) break; + ExpressionTree init = vt.initializer(); + if (!(init instanceof IdentifierTree id)) break; + current = id; + } + return current; } /** diff --git a/engine/src/main/java/com/ibm/engine/language/python/PythonDetectionEngine.java b/engine/src/main/java/com/ibm/engine/language/python/PythonDetectionEngine.java index c56513dba..5e1bb0b3b 100644 --- a/engine/src/main/java/com/ibm/engine/language/python/PythonDetectionEngine.java +++ b/engine/src/main/java/com/ibm/engine/language/python/PythonDetectionEngine.java @@ -26,14 +26,17 @@ import com.ibm.engine.rule.*; import com.ibm.engine.rule.Parameter; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.sonar.plugins.python.api.PythonCheck; import org.sonar.plugins.python.api.PythonVisitorContext; import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.symbols.Usage; import org.sonar.plugins.python.api.tree.*; public class PythonDetectionEngine implements IDetectionEngine { @@ -324,8 +327,10 @@ public boolean isInvocationOnVariable( QualifiedExpression qualifiedExpression = (QualifiedExpression) callee; if (qualifiedExpression.qualifier() instanceof Name name) { - Optional nameString = Optional.of(name).map(Name::symbol).map(Symbol::name); - return nameString.isPresent() && nameString.get().equals(variable.name()); + Symbol symbol = name.symbol(); + if (symbol != null) { + return areSymbolsEquivalent(symbol, variable); + } } return false; @@ -346,7 +351,52 @@ public boolean isInitForVariable(Tree newClass, TraceSymbol variableSymb TraceSymbol traceSymbol = symbolOptional.get(); Symbol symbol = traceSymbol.getSymbol(); - return symbol.name().equals(variable.name()); + if (symbol == null) { + return false; + } + return areSymbolsEquivalent(symbol, variable); + } + + private boolean areSymbolsEquivalent(@Nonnull Symbol s1, @Nonnull Symbol s2) { + if (s1.equals(s2)) { + return true; + } + + Symbol t1 = traceSymbol(s1); + Symbol t2 = traceSymbol(s2); + + return t1.equals(t2); + } + + @Nonnull + private Symbol traceSymbol(@Nonnull Symbol symbol) { + return traceSymbol(symbol, new HashSet<>()); + } + + @Nonnull + private Symbol traceSymbol(@Nonnull Symbol symbol, @Nonnull Set visited) { + if (!visited.add(symbol)) { + return symbol; + } + // NOTE: symbol.usages() iteration order is not guaranteed. For a variable reassigned more + // than once (x = y; x = z; use(x)), this returns whichever ASSIGNMENT_LHS appears first in + // the iteration, which is non-deterministic. Ideally the assignment lexically nearest to + // the use site should be picked. + for (Usage usage : symbol.usages()) { + if (usage.kind() == Usage.Kind.ASSIGNMENT_LHS) { + Tree parent = usage.tree().parent(); + if (parent instanceof AssignmentStatement assignment) { + Expression rhs = assignment.assignedValue(); + if (rhs instanceof Name name) { + Symbol rhsSymbol = name.symbol(); + if (rhsSymbol != null) { + return traceSymbol(rhsSymbol, visited); + } + } + } + } + } + return symbol; } private void analyseExpression( diff --git a/java/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.java b/java/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.java new file mode 100644 index 000000000..409d0b29b --- /dev/null +++ b/java/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.java @@ -0,0 +1,23 @@ +package com.ibm.example; + +public class Issue8IntermediaryVariableTestFile { + + public class Car { + public Car(SeatInterface seat) {} + } + public interface SeatInterface {} + public class LeatherSeats implements SeatInterface {} + public class HeatedSeats implements SeatInterface {} + + public void test() { + LeatherSeats s = new LeatherSeats(); + SeatInterface intermediary = s; + Car c1 = new Car(s); // Noncompliant {{Car}} + Car c2 = new Car(intermediary); // Noncompliant {{Car}} + + // chain length > 1: b -> a -> s + SeatInterface a = s; + SeatInterface b = a; + Car c3 = new Car(b); // Noncompliant {{Car}} + } +} diff --git a/java/src/test/files/rules/issues/Issue8MethodReceiverTestFile.java b/java/src/test/files/rules/issues/Issue8MethodReceiverTestFile.java new file mode 100644 index 000000000..7e080e1f2 --- /dev/null +++ b/java/src/test/files/rules/issues/Issue8MethodReceiverTestFile.java @@ -0,0 +1,26 @@ +package com.ibm.example; + +public class Issue8MethodReceiverTestFile { + + public interface SeatInterface { + String describe(); + } + + public class Car { + public Car(SeatInterface seat) {} + } + + public class LeatherSeats implements SeatInterface { + public String describe() { + return "leather"; + } + } + + public void test() { + LeatherSeats s = new LeatherSeats(); + SeatInterface intermediary = s; + // s.describe() exercises isInvocationOnVariable: receiver 's' traces to 'intermediary' + s.describe(); + Car c = new Car(intermediary); // Noncompliant {{Car}} + } +} diff --git a/java/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java b/java/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java new file mode 100644 index 000000000..7288a682b --- /dev/null +++ b/java/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java @@ -0,0 +1,127 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.IDetectionContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.plugins.java.api.JavaCheck; +import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.Tree; + +class Issue8IntermediaryVariableTest extends TestBase { + + static IDetectionContext detectionContext = + new IDetectionContext() { + @Nonnull + @Override + public Class type() { + return IDetectionContext.class; + } + }; + + public static List> seatRules = + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "com.ibm.example.Issue8IntermediaryVariableTestFile$LeatherSeats") + .forConstructor() + .shouldBeDetectedAs(new ValueActionFactory<>("LeatherSeats")) + .withoutParameters() + .buildForContext(detectionContext) + .inBundle(() -> "testBundle") + .withoutDependingDetectionRules()); + + public Issue8IntermediaryVariableTest() { + super( + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "com.ibm.example.Issue8IntermediaryVariableTestFile$Car") + .forConstructor() + .shouldBeDetectedAs(new ValueActionFactory<>("Car")) + .withMethodParameter( + "com.ibm.example.Issue8IntermediaryVariableTestFile$SeatInterface") + .addDependingDetectionRules(seatRules) + .buildForContext(detectionContext) + .inBundle(() -> "testBundle") + .withoutDependingDetectionRules())); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(ValueAction.class); + assertThat(value0.asString()).isEqualTo("Car"); + + List> stores = + getStoresOfValueType(ValueAction.class, detectionStore.getChildren()); + + assertThat(stores).hasSize(1); + + DetectionStore store_1 = stores.get(0); + assertThat(store_1.getDetectionValues()).hasSize(1); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(ValueAction.class); + assertThat(value0_1.asString()).isEqualTo("LeatherSeats"); + } + + @Override + public void update( + @Nonnull + com.ibm.engine.detection.Finding< + JavaCheck, Tree, Symbol, JavaFileScannerContext> + finding) { + super.update(finding); + finding.detectionStore() + .getDetectionValues() + .forEach( + iValue -> { + this.reportIssue(iValue.getLocation(), iValue.asString()); + }); + } + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile("src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.java") + .withChecks(this) + .verifyIssues(); + } +} diff --git a/java/src/test/java/com/ibm/plugin/rules/issues/Issue8MethodReceiverTest.java b/java/src/test/java/com/ibm/plugin/rules/issues/Issue8MethodReceiverTest.java new file mode 100644 index 000000000..a1e3aa3ab --- /dev/null +++ b/java/src/test/java/com/ibm/plugin/rules/issues/Issue8MethodReceiverTest.java @@ -0,0 +1,125 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.IDetectionContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.plugins.java.api.JavaCheck; +import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.Tree; + +class Issue8MethodReceiverTest extends TestBase { + + static IDetectionContext detectionContext = + new IDetectionContext() { + @Nonnull + @Override + public Class type() { + return IDetectionContext.class; + } + }; + + // Child rule: detects describe() called on a SeatInterface — exercises isInvocationOnVariable + public static List> seatRules = + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "com.ibm.example.Issue8MethodReceiverTestFile$SeatInterface") + .forMethods("describe") + .shouldBeDetectedAs(new ValueActionFactory<>("describe")) + .withoutParameters() + .buildForContext(detectionContext) + .inBundle(() -> "testBundle") + .withoutDependingDetectionRules()); + + public Issue8MethodReceiverTest() { + super( + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("com.ibm.example.Issue8MethodReceiverTestFile$Car") + .forConstructor() + .shouldBeDetectedAs(new ValueActionFactory<>("Car")) + .withMethodParameter( + "com.ibm.example.Issue8MethodReceiverTestFile$SeatInterface") + .addDependingDetectionRules(seatRules) + .buildForContext(detectionContext) + .inBundle(() -> "testBundle") + .withoutDependingDetectionRules())); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(ValueAction.class); + assertThat(value0.asString()).isEqualTo("Car"); + + // The child was found via isInvocationOnVariable: s.describe() where s traces to + // intermediary + List> stores = + getStoresOfValueType(ValueAction.class, detectionStore.getChildren()); + assertThat(stores).hasSize(1); + + DetectionStore store_1 = stores.get(0); + assertThat(store_1.getDetectionValues()).hasSize(1); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(ValueAction.class); + assertThat(value0_1.asString()).isEqualTo("describe"); + } + + @Override + public void update( + @Nonnull + com.ibm.engine.detection.Finding< + JavaCheck, Tree, Symbol, JavaFileScannerContext> + finding) { + super.update(finding); + finding.detectionStore() + .getDetectionValues() + .forEach(iValue -> this.reportIssue(iValue.getLocation(), iValue.asString())); + } + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile("src/test/files/rules/issues/Issue8MethodReceiverTestFile.java") + .withChecks(this) + .verifyIssues(); + } +} diff --git a/python/src/test/files/rules/issues/Issue400RecursionGuardTestFile.py b/python/src/test/files/rules/issues/Issue400RecursionGuardTestFile.py new file mode 100644 index 000000000..811b14f0e --- /dev/null +++ b/python/src/test/files/rules/issues/Issue400RecursionGuardTestFile.py @@ -0,0 +1,11 @@ +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +key = b"\x00" * 32 +iv = b"\x00" * 16 + +# Mutual assignment cycle: tracing alg reaches intermediary, which traces back to alg. +# Without a visited-set guard in traceSymbol this causes infinite recursion. +alg = algorithms.AES(key) +intermediary = alg +alg = intermediary # cycle: alg -> intermediary -> alg -> ... +cipher = Cipher(alg, modes.CBC(iv)) # Noncompliant diff --git a/python/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.py b/python/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.py new file mode 100644 index 000000000..2888abaca --- /dev/null +++ b/python/src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.py @@ -0,0 +1,9 @@ +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +key = b"\x00" * 32 +iv = b"\x00" * 16 + +# intermediary variable: alg -> algorithms.AES(key) +alg = algorithms.AES(key) +intermediary = alg +cipher = Cipher(intermediary, modes.CBC(iv)) # Noncompliant diff --git a/python/src/test/java/com/ibm/plugin/rules/issues/Issue400RecursionGuardTest.java b/python/src/test/java/com/ibm/plugin/rules/issues/Issue400RecursionGuardTest.java new file mode 100644 index 000000000..75e9ebb8c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/issues/Issue400RecursionGuardTest.java @@ -0,0 +1,63 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class Issue400RecursionGuardTest extends TestBase { + + @Test + @Timeout(value = 10, unit = TimeUnit.SECONDS) + void test() { + // Must complete without StackOverflowError caused by a mutual assignment cycle + // (alg = intermediary; intermediary = alg) in traceSymbol. + PythonCheckVerifier.verify( + "src/test/files/rules/issues/Issue400RecursionGuardTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("AES"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java b/python/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java new file mode 100644 index 000000000..13f9fcc9a --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/issues/Issue8IntermediaryVariableTest.java @@ -0,0 +1,68 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Mode; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class Issue8IntermediaryVariableTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/issues/Issue8IntermediaryVariableTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + // Verify that traceSymbol correctly resolves: intermediary -> alg -> algorithms.AES(key) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("AES"); + + DetectionStore modeStore = + getStoreOfValueType(Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues()).hasSize(1); + IValue modeValue = modeStore.getDetectionValues().get(0); + assertThat(modeValue).isInstanceOf(Mode.class); + assertThat(modeValue.asString()).isEqualTo("CBC"); + } +}