aboutsummaryrefslogtreecommitdiff
path: root/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt
blob: ff68ad94582128baea3f976daab529b7ffc088d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2021 Code Intelligence GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")

package com.code_intelligence.jazzer.instrumentor

import com.code_intelligence.jazzer.api.HookType
import com.code_intelligence.jazzer.api.MethodHook
import com.code_intelligence.jazzer.utils.descriptor
import java.lang.invoke.MethodHandle
import java.lang.reflect.Method
import java.lang.reflect.Modifier

class Hook private constructor(
    private val targetClassName: String,
    val hookType: HookType,
    val targetMethodName: String,
    val targetMethodDescriptor: String?,
    val additionalClassesToHook: List<String>,
    val targetInternalClassName: String,
    private val targetReturnTypeDescriptor: String?,
    private val targetWrappedReturnTypeDescriptor: String?,
    private val hookClassName: String,
    val hookInternalClassName: String,
    val hookMethodName: String,
    val hookMethodDescriptor: String
) {

    override fun toString(): String {
        return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook"
    }

    companion object {
        fun createAndVerifyHook(hookMethod: Method, hookData: MethodHook, className: String): Hook {
            return createHook(hookMethod, hookData, className).also {
                verify(hookMethod, it)
            }
        }

        private fun createHook(hookMethod: Method, annotation: MethodHook, targetClassName: String): Hook {
            val targetReturnTypeDescriptor = annotation.targetMethodDescriptor
                .takeIf { it.isNotBlank() }?.let { extractReturnTypeDescriptor(it) }
            val hookClassName: String = hookMethod.declaringClass.name
            return Hook(
                targetClassName = targetClassName,
                hookType = annotation.type,
                targetMethodName = annotation.targetMethod,
                targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() },
                additionalClassesToHook = annotation.additionalClassesToHook.asList(),
                targetInternalClassName = targetClassName.replace('.', '/'),
                targetReturnTypeDescriptor = targetReturnTypeDescriptor,
                targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) },
                hookClassName = hookClassName,
                hookInternalClassName = hookClassName.replace('.', '/'),
                hookMethodName = hookMethod.name,
                hookMethodDescriptor = hookMethod.descriptor
            )
        }

        private fun verify(hookMethod: Method, potentialHook: Hook) {
            // Verify the hook method's modifiers (public static).
            require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" }
            require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" }

            // Verify the hook method's parameter count.
            val numParameters = hookMethod.parameters.size
            when (potentialHook.hookType) {
                HookType.BEFORE, HookType.REPLACE -> require(numParameters == 4) { "$potentialHook: incorrect number of parameters (expected 4)" }
                HookType.AFTER -> require(numParameters == 5) { "$potentialHook: incorrect number of parameters (expected 5)" }
            }

            // Verify the hook method's parameter types.
            val parameterTypes = hookMethod.parameterTypes
            require(parameterTypes[0] == MethodHandle::class.java) { "$potentialHook: first parameter must have type MethodHandle" }
            require(parameterTypes[1] == Object::class.java || parameterTypes[1].name == potentialHook.targetClassName) { "$potentialHook: second parameter must have type Object or ${potentialHook.targetClassName}" }
            require(parameterTypes[2] == Array<Object>::class.java) { "$potentialHook: third parameter must have type Object[]" }
            require(parameterTypes[3] == Int::class.javaPrimitiveType) { "$potentialHook: fourth parameter must have type int" }

            // Verify the hook method's return type if possible.
            when (potentialHook.hookType) {
                HookType.BEFORE, HookType.AFTER -> require(hookMethod.returnType == Void.TYPE) {
                    "$potentialHook: return type must be void"
                }
                HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) {
                    if (potentialHook.targetMethodName == "<init>") {
                        require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" }
                    } else if (potentialHook.targetReturnTypeDescriptor == "V") {
                        require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void" }
                    } else {
                        require(
                            hookMethod.returnType.descriptor in listOf(
                                java.lang.Object::class.java.descriptor,
                                potentialHook.targetReturnTypeDescriptor,
                                potentialHook.targetWrappedReturnTypeDescriptor
                            )
                        ) {
                            "$potentialHook: return type must have type Object or match the descriptors ${potentialHook.targetReturnTypeDescriptor} or ${potentialHook.targetWrappedReturnTypeDescriptor}"
                        }
                    }
                }
            }

            // AfterMethodHook only: Verify the type of the last parameter if known. Even if not
            // known, it must not be a primitive value.
            if (potentialHook.hookType == HookType.AFTER) {
                if (potentialHook.targetReturnTypeDescriptor != null) {
                    require(
                        parameterTypes[4] == java.lang.Object::class.java ||
                            parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor
                    ) {
                        "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}"
                    }
                } else {
                    require(!parameterTypes[4].isPrimitive) {
                        "$potentialHook: fifth parameter must not be a primitive type, use a boxed type instead"
                    }
                }
            }
        }
    }
}