diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll index 543c392817cf..3eedae0159ba 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll @@ -4,6 +4,7 @@ private import DataFlowUtil private import semmle.code.java.dataflow.InstanceAccess private import semmle.code.java.dataflow.FlowSummary private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch +private import semmle.code.java.dispatch.internal.Unification private module DispatchImpl { /** Gets a viable implementation of the target of the given `Call`. */ @@ -115,74 +116,20 @@ private module DispatchImpl { exact = false and exists(RefType t2 | result.asCallable() = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and - not failsUnification(t, t2) + not Unification::failsUnification(t, t2) ) or result.asSummarizedCallable() = def ) } - pragma[noinline] - private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) { - contextArgHasType(_, _, t1, _) and t1.getGenericType() = g - } - - pragma[noinline] - private predicate unificationTargetRight(ParameterizedType t2, GenericType g) { - exists(VirtualDispatch::viableMethodImpl(_, _, t2)) and t2.getGenericType() = g - } - - private predicate unificationTargets(Type t1, Type t2) { - exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g)) - or - exists(Array a1, Array a2 | - unificationTargets(a1, a2) and - t1 = a1.getComponentType() and - t2 = a2.getComponentType() - ) - or - exists(ParameterizedType pt1, ParameterizedType pt2, int pos | - unificationTargets(pt1, pt2) and - not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and - t1 = pt1.getTypeArgument(pos) and - t2 = pt2.getTypeArgument(pos) - ) - } + private predicate unificationTargetLeft(ParameterizedType t1) { contextArgHasType(_, _, t1, _) } - pragma[noinline] - private predicate typeArgsOfUnificationTargets( - ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2 - ) { - unificationTargets(t1, t2) and - arg1 = t1.getTypeArgument(pos) and - arg2 = t2.getTypeArgument(pos) + private predicate unificationTargetRight(ParameterizedType t2) { + exists(VirtualDispatch::viableMethodImpl(_, _, t2)) } - private predicate failsUnification(Type t1, Type t2) { - unificationTargets(t1, t2) and - ( - exists(RefType arg1, RefType arg2 | - typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and - failsUnification(arg1, arg2) - ) - or - failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType()) - or - not ( - t1 instanceof Array and t2 instanceof Array - or - t1.(PrimitiveType) = t2.(PrimitiveType) - or - t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration() - or - t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration() - or - t1 instanceof BoundedType and t2 instanceof RefType - or - t1 instanceof RefType and t2 instanceof BoundedType - ) - ) - } + private module Unification = MkUnification; private int parameterPosition() { result in [-1, any(Parameter p).getPosition()] } diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index a05553eb3736..29e93f426dcf 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -15,6 +15,7 @@ private import semmle.code.java.dataflow.internal.DataFlowUtil private import semmle.code.java.dataflow.internal.DataFlowPrivate private import semmle.code.java.dataflow.internal.ContainerFlow private import semmle.code.java.dataflow.InstanceAccess +private import semmle.code.java.dispatch.internal.Unification /** * Gets a viable dispatch target for `ma`. This is the input dispatch relation. @@ -266,67 +267,10 @@ Method viableImpl_out(MethodAccess ma) { ) } -private module Unification { - pragma[noinline] - private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) { - objectToStringQualType(_, t1) and t1.getGenericType() = g - } +private predicate unificationTargetLeft(ParameterizedType t1) { objectToStringQualType(_, t1) } - pragma[noinline] - private predicate unificationTargetRight(ParameterizedType t2, GenericType g) { - exists(viableMethodImpl(_, _, t2)) and t2.getGenericType() = g - } - - private predicate unificationTargets(Type t1, Type t2) { - exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g)) - or - exists(Array a1, Array a2 | - unificationTargets(a1, a2) and - t1 = a1.getComponentType() and - t2 = a2.getComponentType() - ) - or - exists(ParameterizedType pt1, ParameterizedType pt2, int pos | - unificationTargets(pt1, pt2) and - not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and - t1 = pt1.getTypeArgument(pos) and - t2 = pt2.getTypeArgument(pos) - ) - } - - pragma[noinline] - private predicate typeArgsOfUnificationTargets( - ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2 - ) { - unificationTargets(t1, t2) and - arg1 = t1.getTypeArgument(pos) and - arg2 = t2.getTypeArgument(pos) - } - - pragma[nomagic] - predicate failsUnification(Type t1, Type t2) { - unificationTargets(t1, t2) and - ( - exists(RefType arg1, RefType arg2 | - typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and - failsUnification(arg1, arg2) - ) - or - failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType()) - or - not ( - t1 instanceof Array and t2 instanceof Array - or - t1.(PrimitiveType) = t2.(PrimitiveType) - or - t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration() - or - t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration() - or - t1 instanceof BoundedType and t2 instanceof RefType - or - t1 instanceof RefType and t2 instanceof BoundedType - ) - ) - } +private predicate unificationTargetRight(ParameterizedType t2) { + exists(viableMethodImpl(_, _, t2)) } + +private module Unification = MkUnification; diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll index 7d4b617ca190..86bb5bbe8adc 100644 --- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll @@ -9,6 +9,7 @@ private import DispatchFlow as DispatchFlow private import ObjFlow as ObjFlow private import semmle.code.java.dataflow.internal.BaseSSA private import semmle.code.java.controlflow.Guards +private import semmle.code.java.dispatch.internal.Unification /** * A conservative analysis that returns a single method - if we can establish @@ -91,69 +92,10 @@ private module Dispatch { ) } - private module Unification_v2 { - pragma[noinline] - private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) { - qualType(_, t1, _) and t1.getGenericType() = g - } + private predicate unificationTargetLeft_v2(ParameterizedType t1) { qualType(_, t1, _) } - pragma[noinline] - private predicate unificationTargetRight(ParameterizedType t2, GenericType g) { - exists(viableMethodImpl(_, _, t2)) and t2.getGenericType() = g - } - - private predicate unificationTargets(Type t1, Type t2) { - exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g)) - or - exists(Array a1, Array a2 | - unificationTargets(a1, a2) and - t1 = a1.getComponentType() and - t2 = a2.getComponentType() - ) - or - exists(ParameterizedType pt1, ParameterizedType pt2, int pos | - unificationTargets(pt1, pt2) and - not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and - t1 = pt1.getTypeArgument(pos) and - t2 = pt2.getTypeArgument(pos) - ) - } - - pragma[noinline] - private predicate typeArgsOfUnificationTargets( - ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2 - ) { - unificationTargets(t1, t2) and - arg1 = t1.getTypeArgument(pos) and - arg2 = t2.getTypeArgument(pos) - } - - predicate failsUnification(Type t1, Type t2) { - unificationTargets(t1, t2) and - ( - exists(RefType arg1, RefType arg2 | - typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and - failsUnification(arg1, arg2) - ) - or - failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType()) - or - not ( - t1 instanceof Array and t2 instanceof Array - or - t1.(PrimitiveType) = t2.(PrimitiveType) - or - t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration() - or - t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration() - or - t1 instanceof BoundedType and t2 instanceof RefType - or - t1 instanceof RefType and t2 instanceof BoundedType - ) - ) - } - } + private module Unification_v2 = + MkUnification; /** * INTERNAL: Use `viableImpl` instead. @@ -203,70 +145,15 @@ private module Dispatch { else result = source.getMethod().getSourceDeclaration() } - private module Unification_v1 { - pragma[noinline] - private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) { - hasQualifierType(_, t1, _) and t1.getGenericType() = g - } + private predicate unificationTargetLeft_v1(ParameterizedType t1) { hasQualifierType(_, t1, _) } - pragma[noinline] - private predicate unificationTargetRight(ParameterizedType t2, GenericType g) { - exists(viableMethodImpl(_, _, t2)) and t2.getGenericType() = g - } - - private predicate unificationTargets(Type t1, Type t2) { - exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g)) - or - exists(Array a1, Array a2 | - unificationTargets(a1, a2) and - t1 = a1.getComponentType() and - t2 = a2.getComponentType() - ) - or - exists(ParameterizedType pt1, ParameterizedType pt2, int pos | - unificationTargets(pt1, pt2) and - not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and - t1 = pt1.getTypeArgument(pos) and - t2 = pt2.getTypeArgument(pos) - ) - } - - pragma[noinline] - private predicate typeArgsOfUnificationTargets( - ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2 - ) { - unificationTargets(t1, t2) and - arg1 = t1.getTypeArgument(pos) and - arg2 = t2.getTypeArgument(pos) - } - - predicate failsUnification(Type t1, Type t2) { - unificationTargets(t1, t2) and - ( - exists(RefType arg1, RefType arg2 | - typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and - failsUnification(arg1, arg2) - ) - or - failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType()) - or - not ( - t1 instanceof Array and t2 instanceof Array - or - t1.(PrimitiveType) = t2.(PrimitiveType) - or - t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration() - or - t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration() - or - t1 instanceof BoundedType and t2 instanceof RefType - or - t1 instanceof RefType and t2 instanceof BoundedType - ) - ) - } + private predicate unificationTargetRight(ParameterizedType t2) { + exists(viableMethodImpl(_, _, t2)) } + private module Unification_v1 = + MkUnification; + private RefType getPreciseType(Expr e) { result = e.(FunctionalExpr).getConstructedType() or diff --git a/java/ql/lib/semmle/code/java/dispatch/internal/Unification.qll b/java/ql/lib/semmle/code/java/dispatch/internal/Unification.qll new file mode 100644 index 000000000000..46bcc3a22017 --- /dev/null +++ b/java/ql/lib/semmle/code/java/dispatch/internal/Unification.qll @@ -0,0 +1,136 @@ +/** + * Provides a module to check whether two `ParameterizedType`s are unifiable. + */ + +import java + +/** Holds if `t` is a relevant type to consider for unification. */ +signature predicate unificationTarget(ParameterizedType t); + +/** + * Given two sets of parameterised types to consider for unification, returns + * the set of pairs that are not unifiable. + */ +module MkUnification { + pragma[noinline] + private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) { + targetLeft(t1) and t1.getGenericType() = g + } + + pragma[noinline] + private predicate unificationTargetRight(ParameterizedType t2, GenericType g) { + targetRight(t2) and t2.getGenericType() = g + } + + private predicate unificationTargets(Type t1, Type t2) { + exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g)) + or + exists(Array a1, Array a2 | + unificationTargets(a1, a2) and + t1 = a1.getComponentType() and + t2 = a2.getComponentType() + ) + or + exists(ParameterizedType pt1, ParameterizedType pt2, int pos | + unificationTargets(pt1, pt2) and + not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and + t1 = pt1.getTypeArgument(pos) and + t2 = pt2.getTypeArgument(pos) + ) + } + + pragma[noinline] + private predicate typeArgsOfUnificationTargets( + ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2 + ) { + unificationTargets(t1, t2) and + arg1 = t1.getTypeArgument(pos) and + arg2 = t2.getTypeArgument(pos) + } + + private RefType getUpperBound(RefType t) { + if t instanceof BoundedType + then result = t.(BoundedType).getAnUltimateUpperBoundType() + else result = t + } + + /** + * Holds if `t1` and `t2` are not unifiable. + * + * Restricted to only consider pairs of types such that `targetLeft(t1)`, + * `targetRight(t2)`, and that both are parameterised instances of the same + * generic type. + */ + pragma[nomagic] + predicate failsUnification(Type t1, Type t2) { + unificationTargets(t1, t2) and + ( + exists(RefType arg1, RefType arg2 | + typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and + failsUnification(arg1, arg2) + ) + or + failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType()) + or + // Can't unify an `extends` bound against a concrete type that doesn't + // descend from that upper bound: + exists(RefType upperbound, RefType other | + t1.(BoundedType).getAnUltimateUpperBoundType().getSourceDeclaration() = upperbound and + t2.(RefType).getSourceDeclaration() = other and + not t2 instanceof BoundedType + or + t2.(BoundedType).getAnUltimateUpperBoundType().getSourceDeclaration() = upperbound and + t1.(RefType).getSourceDeclaration() = other and + not t1 instanceof BoundedType + | + not other.getASourceSupertype*() = upperbound + ) + or + // Can't unify two `extends` bounds that don't intersect: + exists(RefType upperbound1, RefType upperbound2 | + t1.(BoundedType).getAnUltimateUpperBoundType() = upperbound1 and + t2.(BoundedType).getAnUltimateUpperBoundType() = upperbound2 and + notHaveIntersection(upperbound1, upperbound2) + ) + or + // Can't unify `? super X` against `Y` or `_ extends Y` where `Y` isn't an + // ancestor of `X`: + exists(RefType lowerbound, RefType upperbound | + t1.(Wildcard).getLowerBoundType().(RefType).getSourceDeclaration() = lowerbound and + getUpperBound(t2).getSourceDeclaration() = upperbound + or + t2.(Wildcard).getLowerBoundType().(RefType).getSourceDeclaration() = lowerbound and + getUpperBound(t1).getSourceDeclaration() = upperbound + | + not lowerbound instanceof BoundedType and + not lowerbound.getASourceSupertype*() = upperbound + ) + or + // Can't unify `? super T`, where `T` is a type variable `T extends S`, + // with a type that doesn't intersect with `S`: + exists(BoundedType lowerbound, RefType upperbound | + t1.(Wildcard).getLowerBoundType() = lowerbound and + getUpperBound(t2) = upperbound + or + t2.(Wildcard).getLowerBoundType() = lowerbound and + getUpperBound(t1) = upperbound + | + notHaveIntersection(lowerbound.getUpperBoundType(), upperbound) + ) + or + not ( + t1 instanceof Array and t2 instanceof Array + or + t1.(PrimitiveType) = t2.(PrimitiveType) + or + t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration() + or + t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration() + or + t1 instanceof BoundedType and t2 instanceof RefType + or + t1 instanceof RefType and t2 instanceof BoundedType + ) + ) + } +}