1 // Copyright 2021 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") 16 17 package com.code_intelligence.jazzer.instrumentor 18 19 import com.code_intelligence.jazzer.api.HookType 20 import com.code_intelligence.jazzer.api.MethodHook 21 import com.code_intelligence.jazzer.utils.descriptor 22 import java.lang.invoke.MethodHandle 23 import java.lang.reflect.Method 24 import java.lang.reflect.Modifier 25 26 class Hook private constructor( 27 private val targetClassName: String, 28 val hookType: HookType, 29 val targetMethodName: String, 30 val targetMethodDescriptor: String?, 31 val additionalClassesToHook: List<String>, 32 val targetInternalClassName: String, 33 private val targetReturnTypeDescriptor: String?, 34 private val targetWrappedReturnTypeDescriptor: String?, 35 private val hookClassName: String, 36 val hookInternalClassName: String, 37 val hookMethodName: String, 38 val hookMethodDescriptor: String 39 ) { 40 toStringnull41 override fun toString(): String { 42 return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" 43 } 44 45 companion object { createAndVerifyHooknull46 fun createAndVerifyHook(hookMethod: Method, hookData: MethodHook, className: String): Hook { 47 return createHook(hookMethod, hookData, className).also { 48 verify(hookMethod, it) 49 } 50 } 51 createHooknull52 private fun createHook(hookMethod: Method, annotation: MethodHook, targetClassName: String): Hook { 53 val targetReturnTypeDescriptor = annotation.targetMethodDescriptor 54 .takeIf { it.isNotBlank() }?.let { extractReturnTypeDescriptor(it) } 55 val hookClassName: String = hookMethod.declaringClass.name 56 return Hook( 57 targetClassName = targetClassName, 58 hookType = annotation.type, 59 targetMethodName = annotation.targetMethod, 60 targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() }, 61 additionalClassesToHook = annotation.additionalClassesToHook.asList(), 62 targetInternalClassName = targetClassName.replace('.', '/'), 63 targetReturnTypeDescriptor = targetReturnTypeDescriptor, 64 targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) }, 65 hookClassName = hookClassName, 66 hookInternalClassName = hookClassName.replace('.', '/'), 67 hookMethodName = hookMethod.name, 68 hookMethodDescriptor = hookMethod.descriptor 69 ) 70 } 71 verifynull72 private fun verify(hookMethod: Method, potentialHook: Hook) { 73 // Verify the hook method's modifiers (public static). 74 require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" } 75 require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" } 76 77 // Verify the hook method's parameter count. 78 val numParameters = hookMethod.parameters.size 79 when (potentialHook.hookType) { 80 HookType.BEFORE, HookType.REPLACE -> require(numParameters == 4) { "$potentialHook: incorrect number of parameters (expected 4)" } 81 HookType.AFTER -> require(numParameters == 5) { "$potentialHook: incorrect number of parameters (expected 5)" } 82 } 83 84 // Verify the hook method's parameter types. 85 val parameterTypes = hookMethod.parameterTypes 86 require(parameterTypes[0] == MethodHandle::class.java) { "$potentialHook: first parameter must have type MethodHandle" } 87 require(parameterTypes[1] == Object::class.java || parameterTypes[1].name == potentialHook.targetClassName) { "$potentialHook: second parameter must have type Object or ${potentialHook.targetClassName}" } 88 require(parameterTypes[2] == Array<Object>::class.java) { "$potentialHook: third parameter must have type Object[]" } 89 require(parameterTypes[3] == Int::class.javaPrimitiveType) { "$potentialHook: fourth parameter must have type int" } 90 91 // Verify the hook method's return type if possible. 92 when (potentialHook.hookType) { 93 HookType.BEFORE, HookType.AFTER -> require(hookMethod.returnType == Void.TYPE) { 94 "$potentialHook: return type must be void" 95 } 96 HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) { 97 if (potentialHook.targetMethodName == "<init>") { 98 require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" } 99 } else if (potentialHook.targetReturnTypeDescriptor == "V") { 100 require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void" } 101 } else { 102 require( 103 hookMethod.returnType.descriptor in listOf( 104 java.lang.Object::class.java.descriptor, 105 potentialHook.targetReturnTypeDescriptor, 106 potentialHook.targetWrappedReturnTypeDescriptor 107 ) 108 ) { 109 "$potentialHook: return type must have type Object or match the descriptors ${potentialHook.targetReturnTypeDescriptor} or ${potentialHook.targetWrappedReturnTypeDescriptor}" 110 } 111 } 112 } 113 } 114 115 // AfterMethodHook only: Verify the type of the last parameter if known. Even if not 116 // known, it must not be a primitive value. 117 if (potentialHook.hookType == HookType.AFTER) { 118 if (potentialHook.targetReturnTypeDescriptor != null) { 119 require( 120 parameterTypes[4] == java.lang.Object::class.java || 121 parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor 122 ) { 123 "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}" 124 } 125 } else { 126 require(!parameterTypes[4].isPrimitive) { 127 "$potentialHook: fifth parameter must not be a primitive type, use a boxed type instead" 128 } 129 } 130 } 131 } 132 } 133 } 134