/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package ai package domain package l1 import scala.reflect.ClassTag import scala.collection.immutable.ArraySeq import org.opalj.br.BooleanType import org.opalj.br.BootstrapMethod import org.opalj.br.ByteType import org.opalj.br.CharType import org.opalj.br.ClassType import org.opalj.br.DoubleType import org.opalj.br.FieldType import org.opalj.br.FloatType import org.opalj.br.IntegerType import org.opalj.br.InvokeStaticMethodHandle import org.opalj.br.LongType import org.opalj.br.MethodDescriptor import org.opalj.br.MethodDescriptor.ConstantBootstrapsPrimitiveClassDescriptor import org.opalj.br.ReferenceType import org.opalj.br.ShortType import org.opalj.br.Type import org.opalj.value.IsClassValue import org.opalj.value.TheClassValue /** * Enables the tracking of concrete `Class` values. * * This class overrides `invokestatic` and only delegates to the default implementation * if it cannot successfully handle the call. Hence, this trait needs to be mixed in after * the trait that handles the default case but before all other traits that "just" * analyze invokestatic calls. * {{{ * class MyDomain * extends DefaultTypeLevelInvokeInstructions * with ClassValues * with * }}} * * @author Michael Eichberg (fixes for multi-parameter Class.forName(...) calls) * @author Arne Lottmann */ trait ClassValues extends StringValues with FieldAccessesDomain with DynamicLoadsDomain with MethodCallsDomain { domain: CorrelationalDomain & IntegerValuesDomain & TypedValuesFactory & Configuration => type DomainClassValue <: ClassValue & DomainObjectValue val DomainClassValueTag: ClassTag[DomainClassValue] /** * All values (`Class<...> c`) that represent the same type (e.g. `java.lang.String`) * are actually represented by the same class (object) value at runtime. */ protected trait ClassValue extends SObjectValue with IsClassValue { this: DomainClassValue => def value: Type override def doJoinWithNonNullValueWithSameOrigin( joinPC: Int, other: DomainSingleOriginReferenceValue ): Update[DomainSingleOriginReferenceValue] = { other match { case that: ClassValue => if (this.value eq that.value) // Recall: all instances are the same; i.e., // String.class "is reference equal to" Class.forName("java.lang.String") NoUpdate else StructuralUpdate(ObjectValue(origin, No, true, ClassType.Class, nextRefId())) case _ => val result = super.doJoinWithNonNullValueWithSameOrigin(joinPC, other) if (result.isStructuralUpdate) { result } else { // This (class) value and the other value may have a corresponding // abstract representation (w.r.t. the next abstraction level!) // but we still need to drop the concrete information. StructuralUpdate(result.value.update()) } } } override def adapt(target: TargetDomain, targetOrigin: ValueOrigin): target.DomainValue = { target.ClassValue(targetOrigin, this.value) } override def abstractsOver(other: DomainValue): Boolean = { if (this eq other) return true; other match { case that: ClassValue => that.value eq this.value case _ => false } } override def toCanonicalForm: TheClassValue = TheClassValue(value) override def equals(other: Any): Boolean = other match { case cv: ClassValue => super.equals(other) && cv.value == this.value case _ => false } override protected def canEqual(other: SObjectValue): Boolean = { other.isInstanceOf[ClassValue] } override def hashCode: Int = super.hashCode + 71 * value.hashCode override def toString: String = s"Class<${value.toJava}>[↦$origin;refId=$refId]" } // Needs to be implemented since the default implementation does not make sense here override def ClassValue(vo: ValueOrigin, value: Type): DomainObjectValue protected[l1] def simpleClassForNameCall(pc: Int, className: String): MethodCallResult = { if (className.isEmpty) return justThrows(ClassNotFoundException(pc)); val classValue = try { ReferenceType(className.replace('.', '/')) } catch { case _: IllegalArgumentException => // if "className" is not a valid descriptor return justThrows(ClassNotFoundException(pc)); } if (classValue.isClassType) { val classType = classValue.asClassType if (classHierarchy.isKnown(classType) || !throwClassNotFoundException) { ComputedValue(ClassValue(pc, classValue)) } else { val exception = Iterable(ClassNotFoundException(pc)) ComputedValueOrException(ClassValue(pc, classValue), exception) } } else { val elementType = classValue.asArrayType.elementType if (elementType.isBaseType || classHierarchy.isKnown(elementType.asClassType) || !throwClassNotFoundException ) { ComputedValue(ClassValue(pc, classValue)) } else { ComputedValueOrException( ClassValue(pc, classValue), Iterable(ClassNotFoundException(pc)) ) } } } abstract override def invokestatic( pc: Int, declaringClass: ClassType, isInterface: Boolean, name: String, methodDescriptor: MethodDescriptor, operands: Operands ): MethodCallResult = { import org.opalj.ai.domain.l1.ClassValues.* if ((declaringClass eq ClassType.Class) && (name == "forName") && operands.nonEmpty) { operands match { case _ :+ (sv: StringValue) => // Handle forName calls where the first argument is the class FQN val value = sv.value methodDescriptor match { case `forName_String` => simpleClassForNameCall(pc, value) case `forName_String_boolean_ClassLoader` => simpleClassForNameCall(pc, value) case `forName_String_Class` => simpleClassForNameCall(pc, value) case `forName_String_boolean_ClassLoader_Class` => simpleClassForNameCall(pc, value) case _ => throw DomainException( s"unsupported Class { ${methodDescriptor.toJava("forName")} }" ) } case _ :+ (sv: StringValue) :+ _ => // Handle forName calls where the second argument is the class FQN // IMPROVE: If there was tracking for Module values in place, we could validate that the FQN is // actually part of the given module. For now, this is a safe over-approximation. val value = sv.value methodDescriptor match { case `forName_Module_String` => simpleClassForNameCall(pc, value) case `forName_Module_String_Class` => simpleClassForNameCall(pc, value) case _ => throw DomainException( s"unsupported Class { ${methodDescriptor.toJava("forName")} }" ) } case _ => // call default handler (the first and second argument are not a string) super.invokestatic(pc, declaringClass, isInterface, name, methodDescriptor, operands) } } else { // call default handler super.invokestatic(pc, declaringClass, isInterface, name, methodDescriptor, operands) } } abstract override def getstatic( pc: Int, declaringClass: ClassType, name: String, fieldType: FieldType ): Computation[DomainValue, Nothing] = { if (name == "TYPE") { declaringClass match { case ClassType.Boolean => ComputedValue(ClassValue(pc, BooleanType)) case ClassType.Byte => ComputedValue(ClassValue(pc, ByteType)) case ClassType.Character => ComputedValue(ClassValue(pc, CharType)) case ClassType.Short => ComputedValue(ClassValue(pc, ShortType)) case ClassType.Integer => ComputedValue(ClassValue(pc, IntegerType)) case ClassType.Long => ComputedValue(ClassValue(pc, LongType)) case ClassType.Float => ComputedValue(ClassValue(pc, FloatType)) case ClassType.Double => ComputedValue(ClassValue(pc, DoubleType)) case _ => super.getstatic(pc, declaringClass, name, fieldType) } } else { super.getstatic(pc, declaringClass, name, fieldType) } } abstract override def loadDynamic( pc: Int, bootstrapMethod: BootstrapMethod, name: String, descriptor: FieldType ): Computation[DomainValue, Nothing] = { bootstrapMethod match { case BootstrapMethod( InvokeStaticMethodHandle( ClassType.ConstantBootstraps, false, "primitiveClass", ConstantBootstrapsPrimitiveClassDescriptor ), ArraySeq() ) => ComputedValue(ClassValue(pc, FieldType(name))) case _ => super.loadDynamic(pc, bootstrapMethod, name, descriptor) } } object ClassValue { def unapply(value: DomainValue): Option[Type] = { value match { case classValue: ClassValue => Some(classValue.value) case _ => None } } } } private object ClassValues { final val forName_String = MethodDescriptor(ClassType.String, ClassType.Class) final val forName_String_Class = { MethodDescriptor( ArraySeq(ClassType.String, ClassType.Class), ClassType.Class ) } final val forName_String_boolean_ClassLoader = { MethodDescriptor( ArraySeq(ClassType.String, BooleanType, ClassType.ClassLoader), ClassType.Class ) } final val forName_String_boolean_ClassLoader_Class = { MethodDescriptor( ArraySeq(ClassType.String, BooleanType, ClassType.ClassLoader, ClassType.Class), ClassType.Class ) } final val forName_Module_String = { MethodDescriptor( ArraySeq(ClassType.Module, ClassType.String), ClassType.Class ) } final val forName_Module_String_Class = { MethodDescriptor( ArraySeq(ClassType.Module, ClassType.String, ClassType.Class), ClassType.Class ) } }