Skip to content

Commit bf8e6e4

Browse files
committed
fix: reject concrete type args against wildcard pattern
A pattern like `ResponseEntity<?>` previously matched concrete parameterizations such as `ResponseEntity<String>` because wildcards were lowered to an unconstrained "any class" matcher at the type-argument slot. Introduce a dedicated wildcard representation — `TypeNamePattern.WildcardType` in the query language and `SerializedTypeNameMatcher.Wildcard` in the serialized matcher — so a wildcard slot in the pattern matches only a `JIRUnboundWildcard` at the same slot in code.
1 parent 40e93ea commit bf8e6e4

14 files changed

Lines changed: 61 additions & 12 deletions

File tree

core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/SerializedTypeMatching.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ fun SerializedTypeNameMatcher.matchType(
3030
type: JIRType,
3131
erasedMatch: SerializedTypeNameMatcher.(String) -> Boolean,
3232
): Boolean = when {
33+
this is SerializedTypeNameMatcher.Wildcard -> type is JIRUnboundWildcard
34+
3335
this is SerializedTypeNameMatcher.ClassPattern && typeArgs.isEmpty() && type is JIRClassType ->
3436
erasedMatch(type.erasedName()) && type.isRawLike()
3537

core/opentaint-configuration-rules/configuration-rules-jvm/src/main/kotlin/org/opentaint/dataflow/configuration/jvm/serialized/SerializedNameMatcher.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ sealed interface SerializedTypeNameMatcher {
2424

2525
@Serializable
2626
data class Array(val element: SerializedTypeNameMatcher) : SerializedTypeNameMatcher
27+
28+
/**
29+
* Matches only an unbounded Java wildcard (`?`) at a type-argument slot.
30+
* Distinct from an "any" [ClassPattern] so a pattern like `Foo<?>` does not
31+
* match a concrete parameterization like `Foo<String>`.
32+
*/
33+
@Serializable
34+
data object Wildcard : SerializedTypeNameMatcher
2735
}
2836

2937
@Serializable(with = SimpleNameMatcherSerializer::class)

core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,9 @@ class JIRBasicAtomEvaluator(
400400
val nameWithout = name.removeSuffix("[]")
401401
name != nameWithout && element.matchErasedName(nameWithout)
402402
}
403+
// A wildcard matcher is only meaningful at a type-argument slot; it has
404+
// no erased-name projection to compare against a string.
405+
is SerializedTypeNameMatcher.Wildcard -> false
403406
}
404407

405408
private fun ConditionNameMatcher.match(name: String): Boolean = when (this) {

core/opentaint-java-querylang/samples/src/main/java/example/RuleWithWildcardGeneric.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ public void entrypoint() {
3232
}
3333

3434
/**
35-
* ResponseEntity&lt;String&gt; is a concrete parameterized form. In many
36-
* semgrep engines a wildcard <?> is considered to match any concrete
37-
* type; keeping this as a Positive documents current engine behavior.
35+
* ResponseEntity&lt;String&gt; is a concrete parameterized form and must not
36+
* match a wildcard &lt;?&gt; type argument in the rule pattern.
3837
*/
39-
final static class PositiveConcreteAlsoMatches extends RuleWithWildcardGeneric {
38+
final static class NegativeConcreteDoesNotMatch extends RuleWithWildcardGeneric {
4039
@Override
4140
public void entrypoint() {
4241
String data = "tainted";

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/ParamCondition.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ sealed interface TypeNamePattern {
3030
override fun toString(): String = "*"
3131
}
3232

33+
/**
34+
* Java unbounded wildcard `?` as a type argument. Unlike [AnyType], which
35+
* is an unconstrained matcher that subsumes any type, [WildcardType] only
36+
* matches an unbounded wildcard at the corresponding type-argument slot.
37+
*/
38+
@Serializable
39+
data object WildcardType : TypeNamePattern {
40+
override fun toString(): String = "?"
41+
}
42+
3343
@Serializable
3444
data class ArrayType(val element: TypeNamePattern) : TypeNamePattern {
3545
override fun toString(): String = "${element}[]"

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/PatternToActionListConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class PatternToActionListConverter: ActionListBuilder {
219219
val elementTypePattern = transformTypeName(typeName.elementType)
220220
TypeNamePattern.ArrayType(elementTypePattern)
221221
}
222-
is TypeName.WildcardTypeName -> TypeNamePattern.AnyType
222+
is TypeName.WildcardTypeName -> TypeNamePattern.WildcardType
223223
}
224224

225225
private fun transformSimpleTypeName(typeName: TypeName.SimpleTypeName): TypeNamePattern {

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/AutomataToTaintRuleConversion.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,10 @@ private fun TaintRuleGenerationCtx.evaluateFormulaSignature(
628628
is Pattern -> {
629629
TODO("Signature class name pattern")
630630
}
631+
632+
is SerializedTypeNameMatcher.Wildcard -> {
633+
TODO("Signature class is a wildcard")
634+
}
631635
}
632636

633637
builders.mapTo(buildersWithClass) { builder ->
@@ -899,6 +903,10 @@ private fun TaintRuleGenerationCtx.typeMatcher(
899903

900904
is TypeNamePattern.AnyType -> null
901905

906+
is TypeNamePattern.WildcardType -> MetaVarConstraintFormula.Constraint(
907+
SerializedTypeNameMatcher.Wildcard
908+
)
909+
902910
is TypeNamePattern.MetaVar -> {
903911
val constraints = metaVarInfo.constraints[typeName.metaVar]
904912
val constraint = when (constraints) {

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/MethodFormulaSimplifier.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -794,13 +794,18 @@ private fun unifyTypeName(
794794
when (left) {
795795
TypeNamePattern.AnyType -> return right
796796

797+
// WildcardType only unifies with itself (already handled by the
798+
// `left == right` short-circuit above). Anything else is incompatible.
799+
TypeNamePattern.WildcardType -> return null
800+
797801
is TypeNamePattern.PrimitiveName -> return null
798802

799803
is TypeNamePattern.ClassName -> when (right) {
800804
TypeNamePattern.AnyType -> return left
801805

802806
is TypeNamePattern.ArrayType,
803-
is TypeNamePattern.PrimitiveName -> return null
807+
is TypeNamePattern.PrimitiveName,
808+
TypeNamePattern.WildcardType -> return null
804809

805810
is TypeNamePattern.ClassName -> {
806811
if (left.name != right.name) return null
@@ -826,7 +831,8 @@ private fun unifyTypeName(
826831
TypeNamePattern.AnyType -> return left
827832

828833
is TypeNamePattern.ArrayType,
829-
is TypeNamePattern.PrimitiveName -> return null
834+
is TypeNamePattern.PrimitiveName,
835+
TypeNamePattern.WildcardType -> return null
830836

831837
is TypeNamePattern.ClassName -> {
832838
if (left.name.endsWith(right.name)) {
@@ -853,7 +859,8 @@ private fun unifyTypeName(
853859
TypeNamePattern.AnyType -> return left
854860

855861
is TypeNamePattern.ArrayType,
856-
is TypeNamePattern.PrimitiveName -> return null
862+
is TypeNamePattern.PrimitiveName,
863+
TypeNamePattern.WildcardType -> return null
857864

858865
is TypeNamePattern.ClassName -> {
859866
if (!stringMatches(right.name, metaVarInfo.metaVarConstraints[left.metaVar])) return null
@@ -885,7 +892,8 @@ private fun unifyTypeName(
885892
is TypeNamePattern.ClassName,
886893
is TypeNamePattern.FullyQualified,
887894
is TypeNamePattern.MetaVar,
888-
is TypeNamePattern.PrimitiveName -> return null
895+
is TypeNamePattern.PrimitiveName,
896+
TypeNamePattern.WildcardType -> return null
889897
}
890898
}
891899
}

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/TaintAutomataGeneration.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,8 @@ private fun EdgeCondition.isDummyCondition(metaVarInfo: ResolvedMetaVarInfo): Bo
995995
is TypeNamePattern.ArrayType,
996996
is TypeNamePattern.ClassName,
997997
is TypeNamePattern.FullyQualified,
998-
is TypeNamePattern.PrimitiveName -> return false
998+
is TypeNamePattern.PrimitiveName,
999+
TypeNamePattern.WildcardType -> return false
9991000
}
10001001
}
10011002

core/opentaint-java-querylang/src/main/kotlin/org/opentaint/semgrep/pattern/conversion/taint/TaintEdgesGeneration.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ private fun MetaVarCtx.typeNameMetaVars(typeName: TypeNamePattern, metaVars: Bit
363363
}
364364

365365
TypeNamePattern.AnyType,
366+
TypeNamePattern.WildcardType,
366367
is TypeNamePattern.PrimitiveName -> {
367368
// no metavars
368369
}

0 commit comments

Comments
 (0)