diff --git a/core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/SerializedTypeMatching.kt b/core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/SerializedTypeMatching.kt index bb584123d..b549372f0 100644 --- a/core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/SerializedTypeMatching.kt +++ b/core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/SerializedTypeMatching.kt @@ -33,6 +33,7 @@ private fun SerializedTypeNameMatcher.matchTypeArgs( if (args.size != type.typeArguments.size) return false args.zip(type.typeArguments).all { (m, a) -> + if (a.isAnyTypeArg()) return@all true m.matchType(a.erasedName(), resolveType = { a }, erasedMatch) } } @@ -44,6 +45,14 @@ private fun SerializedTypeNameMatcher.matchTypeArgs( } } +/** + * `true` for type arguments that denote "any type" — a raw use's declared + * type variable (e.g. `E` of `List`) or an unbounded wildcard ``. Any + * pattern matcher accepts such a slot because the unknown could be whatever + * the pattern requires. + */ +fun JIRType.isAnyTypeArg(): Boolean = this is JIRTypeVariable || this is JIRUnboundWildcard + /** * Erased class name for matching — drops any generic decoration that * [JIRType.typeName] may carry (e.g. `Map` → `java.util.Map`) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt index 70fd33e36..4937fe8b1 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt @@ -25,6 +25,7 @@ import org.opentaint.dataflow.configuration.jvm.TypeArgMatcher import org.opentaint.dataflow.configuration.jvm.TypeMatches import org.opentaint.dataflow.configuration.jvm.TypeMatchesPattern import org.opentaint.dataflow.configuration.jvm.erasedName +import org.opentaint.dataflow.configuration.jvm.isAnyTypeArg import org.opentaint.dataflow.jvm.ap.ifds.CallPositionValue import org.opentaint.dataflow.jvm.ap.ifds.JIRFactTypeChecker import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis @@ -387,9 +388,12 @@ class JIRBasicAtomEvaluator( is CallPositionValue.VarArgValue -> callVarArgValue(res.value) } - private fun TypeArgMatcher.matchType(type: JIRType): Boolean = when (this) { - is TypeArgMatcher.Class -> matchType(type) - is TypeArgMatcher.Array -> matchType(type) + private fun TypeArgMatcher.matchType(type: JIRType): Boolean { + if (type.isAnyTypeArg()) return true + return when (this) { + is TypeArgMatcher.Class -> matchType(type) + is TypeArgMatcher.Array -> matchType(type) + } } private fun TypeArgMatcher.Class.matchType(type: JIRType): Boolean { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithGenericMetavarArrayArg.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithGenericMetavarArrayArg.java index bf29a93a8..1342b198a 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithGenericMetavarArrayArg.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithGenericMetavarArrayArg.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; import org.springframework.http.ResponseEntity; @RuleSet("example/RuleWithGenericMetavarArrayArg.yaml") @@ -20,10 +19,6 @@ ResponseEntity methodReturningResponseEntityByteArray(String data) { return null; } - /** - * Raw ResponseEntity. Note: pattern is ResponseEntity<$T>, so whether this fires - * depends on whether the engine considers raw as unifiable with a type-arg metavar. - */ @SuppressWarnings("rawtypes") ResponseEntity methodReturningRawResponseEntity(String data) { sink(data); @@ -47,9 +42,9 @@ public void entrypoint() { } /** - * In opentaint the method-decl pattern's return-type check is effectively ignored - * on raw vs. parameterized, so raw ResponseEntity DOES get matched by - * ResponseEntity<$T>. We treat this as a Positive to pin the current behavior. + * The metavar type-arg pattern {@code ResponseEntity<$T>} matches the same + * set of types as {@code ResponseEntity} and the raw form, so a raw use + * of {@code ResponseEntity} matches. */ final static class PositiveRawResponseEntity extends RuleWithGenericMetavarArrayArg { @Override diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithNotInsideDistinctReturnType.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithNotInsideDistinctReturnType.java new file mode 100644 index 000000000..81599726d --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithNotInsideDistinctReturnType.java @@ -0,0 +1,72 @@ +package example; + +import base.RuleSample; +import base.RuleSet; +import org.springframework.http.ResponseEntity; + +/** + * A28. {@code pattern-not-inside} with a return type that differs from + * {@code pattern-inside} must filter on its own return type. + * + *

Rule: {@code pattern-inside ResponseEntity $METHOD(..., String $A, ...)} + * combined with {@code pattern-not-inside ResponseEntity $METHOD(...)}. + * The two method-decl signatures share parameter shape but differ on return + * type. The negative predicate must emit a return-type {@code IsType} clause + * for its own signature; otherwise it would drop the return-type constraint + * and exclude every method matching the parameter shape, masking real + * positives.

+ */ +@RuleSet("example/RuleWithNotInsideDistinctReturnType.yaml") +public abstract class RuleWithNotInsideDistinctReturnType implements RuleSample { + + void sink(String data) {} + + ResponseEntity methodReturningString(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningObject(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningInteger(String data) { + sink(data); + return null; + } + + /** + * {@code } return — the not-inside's return-type {@code } + * must NOT match here, so the rule fires. + */ + final static class PositiveStringReturn extends RuleWithNotInsideDistinctReturnType { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningString(data); + } + } + + /** + * {@code } return — same reasoning. + */ + final static class PositiveObjectReturn extends RuleWithNotInsideDistinctReturnType { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningObject(data); + } + } + + /** + * {@code } return — the not-inside excludes this method. + */ + final static class NegativeIntegerReturn extends RuleWithNotInsideDistinctReturnType { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningInteger(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithObjectTypeArg.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithObjectTypeArg.java new file mode 100644 index 000000000..fa60e237b --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithObjectTypeArg.java @@ -0,0 +1,95 @@ +package example; + +import base.RuleSample; +import base.RuleSet; +import org.springframework.http.ResponseEntity; + +/** + * A25. Concrete-{@code Object} type argument — pattern + * {@code ResponseEntity $METHOD(...)}. + * + *

{@code Object} is the upper bound of the unbounded wildcard {@code ?}, + * so a method declaring its return as {@code ResponseEntity} satisfies + * the {@code ResponseEntity} pattern; the raw form is identical to + * {@code ResponseEntity} and matches as well. Other concrete type + * arguments such as {@code String} or {@code Integer} do NOT match.

+ */ +@RuleSet("example/RuleWithObjectTypeArg.yaml") +public abstract class RuleWithObjectTypeArg implements RuleSample { + + void sink(String data) {} + + ResponseEntity methodReturningResponseEntityObject(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityWildcard(String data) { + sink(data); + return null; + } + + @SuppressWarnings("rawtypes") + ResponseEntity methodReturningRawResponseEntity(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityString(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityInteger(String data) { + sink(data); + return null; + } + + final static class PositiveObjectMatchesObject extends RuleWithObjectTypeArg { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityObject(data); + } + } + + /** + * {@code ResponseEntity} matches the {@code } pattern because + * the unbounded wildcard's upper bound is {@code Object}. + */ + final static class PositiveWildcardMatchesObject extends RuleWithObjectTypeArg { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityWildcard(data); + } + } + + /** + * Raw {@code ResponseEntity} is identical to {@code ResponseEntity}, + * so it matches the {@code } pattern too. + */ + final static class PositiveRawMatchesObject extends RuleWithObjectTypeArg { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningRawResponseEntity(data); + } + } + + final static class NegativeStringTypeArg extends RuleWithObjectTypeArg { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityString(data); + } + } + + final static class NegativeIntegerTypeArg extends RuleWithObjectTypeArg { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityInteger(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithStringTypeArgMatchesRawAndWildcard.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithStringTypeArgMatchesRawAndWildcard.java new file mode 100644 index 000000000..48277b878 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithStringTypeArgMatchesRawAndWildcard.java @@ -0,0 +1,94 @@ +package example; + +import base.RuleSample; +import base.RuleSet; +import org.springframework.http.ResponseEntity; + +/** + * A26. Concrete-{@code String} type argument — pattern + * {@code ResponseEntity $METHOD(...)}. + * + *

Raw {@code ResponseEntity} and {@code ResponseEntity} both denote + * "any type argument", so a concrete pattern like {@code } matches + * both — the unknown type argument could be {@code String}. Other concrete + * type arguments such as {@code Object} or {@code Integer} do NOT match.

+ */ +@RuleSet("example/RuleWithStringTypeArgMatchesRawAndWildcard.yaml") +public abstract class RuleWithStringTypeArgMatchesRawAndWildcard implements RuleSample { + + void sink(String data) {} + + ResponseEntity methodReturningResponseEntityString(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityWildcard(String data) { + sink(data); + return null; + } + + @SuppressWarnings("rawtypes") + ResponseEntity methodReturningRawResponseEntity(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityObject(String data) { + sink(data); + return null; + } + + ResponseEntity methodReturningResponseEntityInteger(String data) { + sink(data); + return null; + } + + final static class PositiveStringMatchesString extends RuleWithStringTypeArgMatchesRawAndWildcard { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityString(data); + } + } + + /** + * {@code ResponseEntity} could be parameterized with {@code String}, so + * the {@code } pattern matches it. + */ + final static class PositiveWildcardMatchesString extends RuleWithStringTypeArgMatchesRawAndWildcard { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityWildcard(data); + } + } + + /** + * Raw {@code ResponseEntity} is identical to {@code ResponseEntity}, so + * the {@code } pattern matches it for the same reason. + */ + final static class PositiveRawMatchesString extends RuleWithStringTypeArgMatchesRawAndWildcard { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningRawResponseEntity(data); + } + } + + final static class NegativeObjectTypeArg extends RuleWithStringTypeArgMatchesRawAndWildcard { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityObject(data); + } + } + + final static class NegativeIntegerTypeArg extends RuleWithStringTypeArgMatchesRawAndWildcard { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityInteger(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardGeneric.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardGeneric.java index 8eaefb0da..7df8985f0 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardGeneric.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardGeneric.java @@ -19,6 +19,17 @@ ResponseEntity methodReturningResponseEntityString(String data) { return null; } + ResponseEntity methodReturningResponseEntityObject(String data) { + sink(data); + return null; + } + + @SuppressWarnings("rawtypes") + ResponseEntity methodReturningRawResponseEntity(String data) { + sink(data); + return null; + } + /** * Wildcard ResponseEntity<?> trivially matches the <?> rule * pattern. @@ -32,10 +43,9 @@ public void entrypoint() { } /** - * ResponseEntity<String> is a concrete parameterization. Java's - * unbounded wildcard `?` is the supertype of any `X`, so `<?>` - * accepts any concrete type argument — `ResponseEntity<String>` - * matches. + * ResponseEntity<String> is a concrete parameterization. The + * unbounded wildcard pattern `<?>` matches every parameterization + * of {@code ResponseEntity}, so this method matches. */ final static class PositiveConcreteMatchesWildcard extends RuleWithWildcardGeneric { @Override @@ -44,4 +54,29 @@ public void entrypoint() { methodReturningResponseEntityString(data); } } + + /** + * ResponseEntity<Object> — the wildcard pattern `<?>` matches + * every parameterization, including {@code Object}. + */ + final static class PositiveObjectMatchesWildcard extends RuleWithWildcardGeneric { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityObject(data); + } + } + + /** + * Raw {@code ResponseEntity} — the pattern `ResponseEntity<?>` and + * the raw form {@code ResponseEntity} have identical meaning, so the raw + * use is matched by the wildcard pattern. + */ + final static class PositiveRawMatchesWildcard extends RuleWithWildcardGeneric { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningRawResponseEntity(data); + } + } } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardPatternNestedAndClassMismatch.java b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardPatternNestedAndClassMismatch.java new file mode 100644 index 000000000..a4467b5ab --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardPatternNestedAndClassMismatch.java @@ -0,0 +1,82 @@ +package example; + +import base.RuleSample; +import base.RuleSet; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Map; + +/** + * A27. Wildcard rule pattern with nested-generic and class-mismatch coverage. + * + *

Rule pattern: {@code ResponseEntity $METHOD(..., String $A, ...)}. + * The wildcard accepts every parameterization of {@code ResponseEntity}, + * including nested generics like {@code ResponseEntity>} and + * {@code ResponseEntity>}. The class portion of the + * pattern still narrows: methods that return a non-{@code ResponseEntity} + * type ({@code List}, {@code String}) must NOT match.

+ */ +@RuleSet("example/RuleWithWildcardPatternNestedAndClassMismatch.yaml") +public abstract class RuleWithWildcardPatternNestedAndClassMismatch implements RuleSample { + + void sink(String data) {} + + ResponseEntity> methodReturningResponseEntityListString(String data) { + sink(data); + return null; + } + + ResponseEntity> methodReturningResponseEntityMapStringInteger(String data) { + sink(data); + return null; + } + + List methodReturningListString(String data) { + sink(data); + return null; + } + + String methodReturningString(String data) { + sink(data); + return null; + } + + final static class PositiveNestedListString extends RuleWithWildcardPatternNestedAndClassMismatch { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityListString(data); + } + } + + final static class PositiveNestedMapStringInteger extends RuleWithWildcardPatternNestedAndClassMismatch { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningResponseEntityMapStringInteger(data); + } + } + + /** + * The pattern's class portion is {@code ResponseEntity}; a method + * returning {@code List} has a different erased class name and + * must NOT match — the wildcard only loosens the type-argument slot, + * not the class name. + */ + final static class NegativeListReturn extends RuleWithWildcardPatternNestedAndClassMismatch { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningListString(data); + } + } + + final static class NegativeStringReturn extends RuleWithWildcardPatternNestedAndClassMismatch { + @Override + public void entrypoint() { + String data = "tainted"; + methodReturningString(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithNotInsideDistinctReturnType.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithNotInsideDistinctReturnType.yaml new file mode 100644 index 000000000..6d16d2c7a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithNotInsideDistinctReturnType.yaml @@ -0,0 +1,19 @@ +rules: + - id: example-RuleWithNotInsideDistinctReturnType + languages: + - java + severity: ERROR + message: match example/RuleWithNotInsideDistinctReturnType + patterns: + - pattern: |- + ... + sink($A); + ... + - pattern-inside: |- + ResponseEntity $METHOD(..., String $A, ...) { + ... + } + - pattern-not-inside: |- + ResponseEntity $METHOD(..., String $A, ...) { + ... + } diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithObjectTypeArg.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithObjectTypeArg.yaml new file mode 100644 index 000000000..0bc78d0b7 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithObjectTypeArg.yaml @@ -0,0 +1,15 @@ +rules: + - id: example-RuleWithObjectTypeArg + languages: + - java + severity: ERROR + message: match example/RuleWithObjectTypeArg + patterns: + - pattern: |- + ... + sink($A); + ... + - pattern-inside: |- + ResponseEntity $METHOD(..., String $A, ...) { + ... + } diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithStringTypeArgMatchesRawAndWildcard.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithStringTypeArgMatchesRawAndWildcard.yaml new file mode 100644 index 000000000..c317ada23 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithStringTypeArgMatchesRawAndWildcard.yaml @@ -0,0 +1,15 @@ +rules: + - id: example-RuleWithStringTypeArgMatchesRawAndWildcard + languages: + - java + severity: ERROR + message: match example/RuleWithStringTypeArgMatchesRawAndWildcard + patterns: + - pattern: |- + ... + sink($A); + ... + - pattern-inside: |- + ResponseEntity $METHOD(..., String $A, ...) { + ... + } diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithWildcardPatternNestedAndClassMismatch.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithWildcardPatternNestedAndClassMismatch.yaml new file mode 100644 index 000000000..9d7f13b5a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/RuleWithWildcardPatternNestedAndClassMismatch.yaml @@ -0,0 +1,15 @@ +rules: + - id: example-RuleWithWildcardPatternNestedAndClassMismatch + languages: + - java + severity: ERROR + message: match example/RuleWithWildcardPatternNestedAndClassMismatch + patterns: + - pattern: |- + ... + sink($A); + ... + - pattern-inside: |- + ResponseEntity $METHOD(..., String $A, ...) { + ... + } diff --git a/core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/AutomataToTaintRuleConversion.kt b/core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/AutomataToTaintRuleConversion.kt index 202439905..d3f9444fd 100644 --- a/core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/AutomataToTaintRuleConversion.kt +++ b/core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/AutomataToTaintRuleConversion.kt @@ -561,15 +561,10 @@ private fun TaintRuleGenerationCtx.evaluateFormulaSignature( } } - val returnType = signature.returnType - if (returnType != null) { - val returnTypeFormula = typeMatcher(returnType, semgrepRuleTrace) - if (returnTypeFormula != null) { - for (builder in buildersWithMethodName) { - builder.conditions += returnTypeFormula.toSerializedCondition { typeNameMatcher -> - SerializedCondition.IsType(typeNameMatcher, PositionBase.Result) - } - } + signature.returnType?.let { returnType -> + val condition = evaluateFormulaSignatureReturnType(returnType, semgrepRuleTrace) + buildersWithMethodName.forEach { builder -> + builder.conditions += condition } } @@ -629,6 +624,16 @@ private fun TaintRuleGenerationCtx.evaluateFormulaSignature( return signature to buildersWithClass } +private fun TaintRuleGenerationCtx.evaluateFormulaSignatureReturnType( + returnType: TypeNamePattern, + semgrepRuleTrace: SemgrepRuleLoadStepTrace, +): SerializedCondition { + val returnTypeFormula = typeMatcher(returnType, semgrepRuleTrace) + return returnTypeFormula.toSerializedCondition { typeNameMatcher -> + SerializedCondition.IsType(typeNameMatcher, PositionBase.Result) + } +} + private fun TaintRuleGenerationCtx.evaluateFormulaSignatureMethodName( methodName: SignatureName, semgrepRuleTrace: SemgrepRuleLoadStepTrace, @@ -812,6 +817,10 @@ private fun TaintRuleGenerationCtx.evaluateMethodSignatureCondition( SerializedCondition.and(cond) } + + signature.returnType?.let { + conditions += evaluateFormulaSignatureReturnType(it, semgrepRuleTrace) + } } private fun findMetaVarPosition( diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/TypeAwarePatternTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/TypeAwarePatternTest.kt index 8eb7e0d87..be84b002d 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/TypeAwarePatternTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/TypeAwarePatternTest.kt @@ -24,9 +24,9 @@ class TypeAwarePatternTest : SampleBasedTest() { @Test fun `A1 - ResponseEntity of byte array return type`() = runTest() - // A2. ResponseEntity<$T> — metavar type arg resolving to any concrete type, - // including arrays and the raw form. All three method-decl forms are expected - // to match. + // A2. ResponseEntity<$T> — metavar type arg matches the same set of types + // as `` and the raw form, so all three method-decl forms match + // (String, byte[], and raw). @Test fun `A2 - ResponseEntity metavar matches parameterized string, byte array, and raw`() = runTest() @@ -40,9 +40,9 @@ class TypeAwarePatternTest : SampleBasedTest() { @Test fun `A4 - two-arg generic Map of K V in parameter`() = runTest() - // A5. Wildcard type argument: ResponseEntity. Java's `?` is the - // supertype of any concrete parameterization, so `` accepts both - // ResponseEntity and ResponseEntity. + // A5. Wildcard type argument: ResponseEntity. The pattern matches + // every parameterization of ResponseEntity, including , , + // , and the raw form (raw and `` denote the same set of types). @Test fun `A5 - wildcard type argument ResponseEntity of question mark`() = runTest() @@ -110,6 +110,42 @@ class TypeAwarePatternTest : SampleBasedTest() { fun `A23 - array dimension mismatch String two dim`() = runTest() + // A25. Concrete-Object type argument: ResponseEntity. + // `Object` is the upper bound of `?`, so methods returning + // `ResponseEntity`, `ResponseEntity`, and the raw form match. + // Methods returning `ResponseEntity` or `ResponseEntity` + // do not. + @Test + fun `A25 - Object type argument matches Object wildcard and raw but not other concrete`() = + runTest() + + // A26. Concrete-String type argument: ResponseEntity. + // Raw `ResponseEntity` and `ResponseEntity` denote "any type + // argument", so the `` pattern matches both — the unknown type + // could be `String`. Other concrete type arguments such as `Object` or + // `Integer` do NOT match. + @Test + fun `A26 - String type argument matches String wildcard and raw but not other concrete`() = + runTest() + + // A27. Wildcard rule pattern: `ResponseEntity`. The wildcard accepts + // every parameterization including nested generics + // (`ResponseEntity>`, `ResponseEntity>`), + // but the class portion still narrows — methods returning a + // non-`ResponseEntity` type (`List`, `String`) must NOT match. + @Test + fun `A27 - wildcard pattern matches nested generic ResponseEntity but not other classes`() = + runTest() + + // A28. `pattern-not-inside` whose method-decl return type differs from + // `pattern-inside` must filter on its own return type. Without the + // return-type `IsType` clause, the negative predicate drops its return + // type and excludes every method sharing the parameter shape, masking + // real positives. + @Test + fun `A28 - pattern-not-inside with distinct return type filters on its own return`() = + runTest() + @AfterAll fun close() { closeRunner()