mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
410 lines
14 KiB
Plaintext
410 lines
14 KiB
Plaintext
/**
|
|
* Provides classes and predicates for working with Java Reflection.
|
|
*/
|
|
overlay[local?]
|
|
module;
|
|
|
|
import java
|
|
import JDKAnnotations
|
|
import Serializability
|
|
import semmle.code.java.dataflow.DefUse
|
|
|
|
/** Holds if `f` is a field that may be read by reflection. */
|
|
predicate reflectivelyRead(Field f) {
|
|
f instanceof SerializableField or
|
|
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
|
|
referencedInXmlFile(f)
|
|
}
|
|
|
|
/** Holds if `f` is a field that may be written by reflection. */
|
|
predicate reflectivelyWritten(Field f) {
|
|
f instanceof DeserializableField or
|
|
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
|
|
referencedInXmlFile(f)
|
|
}
|
|
|
|
/**
|
|
* Holds if a field's name and declaring type are referenced in an XML file.
|
|
* Usually, this implies that the field may be accessed reflectively.
|
|
*/
|
|
predicate referencedInXmlFile(Field f) {
|
|
elementReferencingField(f).getParent*() = elementReferencingType(f.getDeclaringType())
|
|
}
|
|
|
|
/**
|
|
* Gets an XML element with an attribute whose value is the name of `f`,
|
|
* suggesting that it might reference `f`.
|
|
*/
|
|
private XmlElement elementReferencingField(Field f) {
|
|
exists(elementReferencingType(f.getDeclaringType())) and
|
|
result.getAnAttribute().getValue() = f.getName()
|
|
}
|
|
|
|
/**
|
|
* Gets an XML element with an attribute whose value is the fully qualified
|
|
* name of `rt`, suggesting that it might reference `rt`.
|
|
*/
|
|
private XmlElement elementReferencingType(RefType rt) {
|
|
result.getAnAttribute().getValue() = rt.getSourceDeclaration().getQualifiedName()
|
|
}
|
|
|
|
abstract private class ReflectiveClassIdentifier extends Expr {
|
|
/**
|
|
* Gets the type of a class identified by this expression.
|
|
*/
|
|
abstract RefType getReflectivelyIdentifiedClass();
|
|
}
|
|
|
|
private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
|
|
override RefType getReflectivelyIdentifiedClass() {
|
|
result = this.getReferencedType().(RefType).getSourceDeclaration()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to a Java standard library method which constructs or returns a `Class<T>` from a `String`.
|
|
*/
|
|
class ReflectiveClassIdentifierMethodCall extends ReflectiveClassIdentifier, MethodCall {
|
|
ReflectiveClassIdentifierMethodCall() {
|
|
// A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class<T>`.
|
|
this.getCallee().getDeclaringType() instanceof TypeClass and this.getCallee().hasName("forName")
|
|
or
|
|
// A call to `ClassLoader.loadClass(...)`, from which we can infer `T` in the returned type `Class<T>`.
|
|
this.getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and
|
|
this.getCallee().hasName("loadClass")
|
|
}
|
|
|
|
/**
|
|
* If the argument to this call is a `StringLiteral`, then return that string.
|
|
*/
|
|
string getTypeName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
|
|
|
override RefType getReflectivelyIdentifiedClass() {
|
|
// We only handle cases where the class is specified as a string literal to this call.
|
|
result.getQualifiedName() = this.getTypeName()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a `ReflectiveClassIdentifier` that we believe may represent the value of `expr`.
|
|
*/
|
|
private ReflectiveClassIdentifier pointsToReflectiveClassIdentifier(Expr expr) {
|
|
// If this is an expression creating a `Class<T>`, return the inferred `T` from the creation expression.
|
|
result = expr
|
|
or
|
|
// Or if this is an access of a variable which was defined as an expression creating a `Class<T>`,
|
|
// return the inferred `T` from the definition expression.
|
|
exists(VarRead use, VariableAssign assign |
|
|
use = expr and
|
|
defUsePair(assign, use) and
|
|
// The source of the assignment must be a `ReflectiveClassIdentifier`.
|
|
result = assign.getSource()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `type` is considered to be "overly" generic.
|
|
*/
|
|
private predicate overlyGenericType(Type type) {
|
|
type instanceof TypeObject or
|
|
type instanceof TypeSerializable
|
|
}
|
|
|
|
/**
|
|
* Identify "catch-all" bounded types, where the upper bound is an overly generic type, such as
|
|
* `? extends Object` and `? extends Serializable`.
|
|
*/
|
|
private predicate catchallType(BoundedType type) {
|
|
exists(Type upperBound | upperBound = type.getUpperBoundType() | overlyGenericType(upperBound))
|
|
}
|
|
|
|
/**
|
|
* Given `Class<X>` or `Constructor<X>`, return all types `T`, such that
|
|
* `Class<T>` or `Constructor<T>` is, or is a sub-type of, `type`.
|
|
*
|
|
* In the case that `X` is a bounded type with an upper bound, and that upper bound is `Object` or
|
|
* `Serializable`, we return no sub-types.
|
|
*/
|
|
pragma[nomagic]
|
|
private Type parameterForSubTypes(ParameterizedType type) {
|
|
(type instanceof TypeClass or type instanceof TypeConstructor) and
|
|
// Only report "real" types.
|
|
not result instanceof TypeVariable and
|
|
// Identify which types the type argument `arg` could represent.
|
|
exists(Type arg |
|
|
arg = type.getTypeArgument(0) and
|
|
// Must not be a catch-all.
|
|
not catchallType(arg)
|
|
|
|
|
// Simple case - this type is not a bounded type, so must represent exactly the `arg` class.
|
|
not arg instanceof BoundedType and result = arg
|
|
or
|
|
exists(RefType upperBound |
|
|
// Upper bound case
|
|
upperBound = arg.(BoundedType).getUpperBoundType()
|
|
|
|
|
// `T extends Foo` implies that `Foo`, or any sub-type of `Foo`, may be represented.
|
|
result.(RefType).getAnAncestor() = upperBound
|
|
)
|
|
or
|
|
exists(RefType lowerBound |
|
|
// Lower bound case
|
|
lowerBound = arg.(Wildcard).getLowerBoundType()
|
|
|
|
|
// `T super Foo` implies that `Foo`, or any super-type of `Foo`, may be represented.
|
|
lowerBound.getAnAncestor() = result
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Given an expression whose type is `Class<T>`, infer a possible set of types for `T`.
|
|
*/
|
|
Type inferClassParameterType(Expr expr) {
|
|
// Must be of type `Class` or `Class<T>`.
|
|
expr.getType() instanceof TypeClass and
|
|
(
|
|
// If this `expr` is a `VarAccess` of a final or effectively final parameter, then look at the
|
|
// arguments to calls to this method, to see if we can infer anything from that case.
|
|
exists(Parameter p |
|
|
p = expr.(VarAccess).getVariable() and
|
|
p.isEffectivelyFinal()
|
|
|
|
|
result = inferClassParameterType(p.getAnArgument())
|
|
)
|
|
or
|
|
if exists(pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass())
|
|
then
|
|
// We've been able to identify where this `Class` instance was created, and identified the
|
|
// particular class that was loaded.
|
|
result = pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass()
|
|
else
|
|
// If we haven't been able to find where the value for this expression was defined, then we
|
|
// resort to the type `T` in `Class<T>`.
|
|
//
|
|
// If `T` refers to a bounded type with an upper bound, then we return all sub-types of the upper
|
|
// bound as possibilities for the instantiation, so long as this is not a catch-all type.
|
|
//
|
|
// A "catch-all" type is something like `? extends Object` or `? extends Serialization`, which
|
|
// would return too many sub-types.
|
|
result = parameterForSubTypes(expr.getType())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Given an expression whose type is `Constructor<T>`, infer a possible set of types for `T`.
|
|
*/
|
|
private Type inferConstructorParameterType(Expr expr) {
|
|
expr.getType() instanceof TypeConstructor and
|
|
// Return all the possible sub-types that could be instantiated.
|
|
// Not a catch-all `Constructor`, for example, `? extends Object` or `? extends Serializable`.
|
|
result = parameterForSubTypes(expr.getType())
|
|
}
|
|
|
|
/**
|
|
* Holds if a `Constructor.newInstance(...)` call for this type would expect an enclosing instance
|
|
* argument in the first position.
|
|
*/
|
|
private predicate expectsEnclosingInstance(RefType r) {
|
|
r instanceof NestedType and
|
|
not r.(NestedType).isStatic()
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.newInstance()` or `Constructor.newInstance()`.
|
|
*/
|
|
class NewInstance extends MethodCall {
|
|
NewInstance() {
|
|
(
|
|
this.getCallee().getDeclaringType() instanceof TypeClass or
|
|
this.getCallee().getDeclaringType() instanceof TypeConstructor
|
|
) and
|
|
this.getCallee().hasName("newInstance")
|
|
}
|
|
|
|
/**
|
|
* Gets the `Constructor` that we believe will be invoked when this `newInstance()` method is
|
|
* called.
|
|
*/
|
|
Constructor getInferredConstructor() {
|
|
result = this.getInferredConstructedType().getAConstructor() and
|
|
if this.getCallee().getDeclaringType() instanceof TypeClass
|
|
then result.getNumberOfParameters() = 0
|
|
else
|
|
if this.getNumArgument() = 1 and this.getArgument(0).getType() instanceof Array
|
|
then
|
|
// This is a var-args array argument. If array argument is initialized inline, then identify
|
|
// the number of arguments specified in the array.
|
|
if exists(this.getArgument(0).(ArrayCreationExpr).getInit())
|
|
then
|
|
// Count the number of elements in the initializer, and find the matching constructors.
|
|
this.matchConstructorArguments(result,
|
|
count(this.getArgument(0).(ArrayCreationExpr).getInit().getAnInit()))
|
|
else
|
|
// Could be any of the constructors on this class.
|
|
any()
|
|
else
|
|
// No var-args in play, just use the number of arguments to the `newInstance(..)` to determine
|
|
// which constructors may be called.
|
|
this.matchConstructorArguments(result, this.getNumArgument())
|
|
}
|
|
|
|
/**
|
|
* Use the number of arguments to a `newInstance(..)` call to determine which constructor might be
|
|
* called.
|
|
*
|
|
* If the `Constructor` is for a non-static nested type, an extra argument is expected to be
|
|
* provided for the enclosing instance.
|
|
*/
|
|
private predicate matchConstructorArguments(Constructor c, int numArguments) {
|
|
if expectsEnclosingInstance(c.getDeclaringType())
|
|
then c.getNumberOfParameters() = numArguments - 1
|
|
else c.getNumberOfParameters() = numArguments
|
|
}
|
|
|
|
/**
|
|
* Gets an inferred type for the constructed class.
|
|
*
|
|
* To infer the constructed type we infer a type `T` for `Class<T>` or `Constructor<T>`, by inspecting
|
|
* points to results.
|
|
*/
|
|
RefType getInferredConstructedType() {
|
|
// Inferred type cannot be abstract.
|
|
not result.isAbstract() and
|
|
// `TypeVariable`s cannot be constructed themselves.
|
|
not result instanceof TypeVariable and
|
|
(
|
|
// If this is called on a `Class<T>` instance, return the inferred type `T`.
|
|
result = inferClassParameterType(this.getQualifier())
|
|
or
|
|
// If this is called on a `Constructor<T>` instance, return the inferred type `T`.
|
|
result = inferConstructorParameterType(this.getQualifier())
|
|
or
|
|
// If the result of this is cast to a particular type, then use that type.
|
|
result = this.getCastInferredConstructedTypes()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* If the result of this `newInstance` call is casted, infer the types that we could have
|
|
* constructed based on the cast. If the cast is to `Object` or `Serializable`, then we ignore the
|
|
* cast.
|
|
*/
|
|
private Type getCastInferredConstructedTypes() {
|
|
exists(CastExpr cast | cast.getExpr() = this |
|
|
result = cast.getType()
|
|
or
|
|
// If we cast the result of this method, then this is either the type specified, or a
|
|
// sub-type of that type. Make sure we exclude overly generic types such as `Object`.
|
|
not overlyGenericType(cast.getType()) and
|
|
hasDescendant(cast.getType(), result)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A `MethodCall` on a `Class` instance.
|
|
*/
|
|
class ClassMethodCall extends MethodCall {
|
|
ClassMethodCall() { this.getCallee().getDeclaringType() instanceof TypeClass }
|
|
|
|
/**
|
|
* Gets an inferred type for the `Class` represented by this expression.
|
|
*/
|
|
RefType getInferredClassType() {
|
|
// `TypeVariable`s do not have methods themselves.
|
|
not result instanceof TypeVariable and
|
|
// If this is called on a `Class<T>` instance, return the inferred type `T`.
|
|
result = inferClassParameterType(this.getQualifier())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.getConstructors(..)` or `Class.getDeclaredConstructors(..)`.
|
|
*/
|
|
class ReflectiveGetConstructorsCall extends ClassMethodCall {
|
|
ReflectiveGetConstructorsCall() {
|
|
this.getCallee().hasName("getConstructors") or
|
|
this.getCallee().hasName("getDeclaredConstructors")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.getMethods(..)` or `Class.getDeclaredMethods(..)`.
|
|
*/
|
|
class ReflectiveGetMethodsCall extends ClassMethodCall {
|
|
ReflectiveGetMethodsCall() {
|
|
this.getCallee().hasName("getMethods") or
|
|
this.getCallee().hasName("getDeclaredMethods")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.getMethod(..)` or `Class.getDeclaredMethod(..)`.
|
|
*/
|
|
class ReflectiveGetMethodCall extends ClassMethodCall {
|
|
ReflectiveGetMethodCall() {
|
|
this.getCallee().hasName("getMethod") or
|
|
this.getCallee().hasName("getDeclaredMethod")
|
|
}
|
|
|
|
/**
|
|
* Gets a `Method` that is inferred to be accessed by this reflective use of `getMethod(..)`.
|
|
*/
|
|
Method inferAccessedMethod() {
|
|
(
|
|
if this.getCallee().hasName("getDeclaredMethod")
|
|
then
|
|
// The method must be declared on the type itself.
|
|
result.getDeclaringType() = this.getInferredClassType()
|
|
else (
|
|
// The method must be public, and declared or inherited by the inferred class type.
|
|
result.isPublic() and
|
|
this.getInferredClassType().inherits(result)
|
|
)
|
|
) and
|
|
// Only consider instances where the method name is provided as a `StringLiteral`.
|
|
result.hasName(this.getArgument(0).(StringLiteral).getValue())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.getAnnotation(..)`.
|
|
*/
|
|
class ReflectiveGetAnnotationCall extends ClassMethodCall {
|
|
ReflectiveGetAnnotationCall() { this.getCallee().hasName("getAnnotation") }
|
|
|
|
/**
|
|
* Gets a possible annotation type for this reflective annotation access.
|
|
*/
|
|
AnnotationType getAPossibleAnnotationType() {
|
|
result = inferClassParameterType(this.getArgument(0))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `Class.getField(..)` that accesses a field.
|
|
*/
|
|
class ReflectiveGetFieldCall extends ClassMethodCall {
|
|
ReflectiveGetFieldCall() {
|
|
this.getCallee().hasName("getField") or
|
|
this.getCallee().hasName("getDeclaredField")
|
|
}
|
|
|
|
/** Gets the field accessed by this call. */
|
|
Field inferAccessedField() {
|
|
(
|
|
if this.getCallee().hasName("getDeclaredField")
|
|
then
|
|
// Declared fields must be on the type itself.
|
|
result.getDeclaringType() = this.getInferredClassType()
|
|
else (
|
|
// This field must be public, and be inherited by one of the inferred class types.
|
|
result.isPublic() and
|
|
this.getInferredClassType().inherits(result)
|
|
)
|
|
) and
|
|
result.hasName(this.getArgument(0).(StringLiteral).getValue())
|
|
}
|
|
}
|