<lambda>null1 // 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 package com.code_intelligence.jazzer.instrumentor
16
17 import com.code_intelligence.jazzer.runtime.CoverageMap
18 import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.Analyzer
19 import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.ICoverageVisitor
20 import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore
21 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesAdapter
22 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesVisitor
23 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IClassProbesAdapterFactory
24 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.JavaNoThrowMethods
25 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ClassInstrumenter
26 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeArrayStrategy
27 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeInserterFactory
28 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.InstrSupport
29 import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ProbeInserter
30 import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader
31 import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassVisitor
32 import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter
33 import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor
34 import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
35 import kotlin.math.max
36
37 class EdgeCoverageInstrumentor(
38 private val initialEdgeId: Int,
39 private val coverageMapClass: Class<*> = CoverageMap::class.java
40 ) : Instrumentor {
41 private var nextEdgeId = initialEdgeId
42 private val coverageMapInternalClassName = coverageMapClass.name.replace('.', '/')
43 init {
44 if (isTesting) {
45 JavaNoThrowMethods.isTesting = true
46 }
47 }
48
49 override fun instrument(bytecode: ByteArray): ByteArray {
50 val reader = InstrSupport.classReaderFor(bytecode)
51 val writer = ClassWriter(reader, 0)
52 val version = InstrSupport.getMajorVersion(reader)
53 val visitor = EdgeCoverageClassProbesAdapter(
54 ClassInstrumenter(edgeCoverageProbeArrayStrategy, edgeCoverageProbeInserterFactory, writer),
55 InstrSupport.needsFrames(version)
56 )
57 reader.accept(visitor, ClassReader.EXPAND_FRAMES)
58 return writer.toByteArray()
59 }
60
61 fun analyze(executionData: ExecutionDataStore, coverageVisitor: ICoverageVisitor, bytecode: ByteArray, internalClassName: String) {
62 Analyzer(executionData, coverageVisitor, edgeCoverageClassProbesAdapterFactory).run {
63 analyzeClass(bytecode, internalClassName)
64 }
65 }
66
67 val numEdges
68 get() = nextEdgeId - initialEdgeId
69
70 private val isTesting
71 get() = coverageMapClass != CoverageMap::class.java
72
73 private fun nextEdgeId(): Int {
74 if (nextEdgeId >= CoverageMap.mem.capacity()) {
75 if (!isTesting) {
76 CoverageMap.enlargeCoverageMap()
77 }
78 }
79 return nextEdgeId++
80 }
81
82 /**
83 * The maximal number of stack elements used by [loadCoverageMap].
84 */
85 private val loadCoverageMapStackSize = 1
86
87 /**
88 * Inject bytecode that loads the coverage map into local variable [variable].
89 */
90 private fun loadCoverageMap(mv: MethodVisitor, variable: Int) {
91 mv.apply {
92 visitFieldInsn(
93 Opcodes.GETSTATIC,
94 coverageMapInternalClassName,
95 "mem",
96 "Ljava/nio/ByteBuffer;"
97 )
98 // Stack: mem (maxStack: 1)
99 visitVarInsn(Opcodes.ASTORE, variable)
100 }
101 }
102
103 /**
104 * The maximal number of stack elements used by [instrumentControlFlowEdge].
105 */
106 private val instrumentControlFlowEdgeStackSize = 5
107
108 /**
109 * Inject bytecode instrumentation on a control flow edge with ID [edgeId]. The coverage map can be loaded from
110 * local variable [variable].
111 */
112 private fun instrumentControlFlowEdge(mv: MethodVisitor, edgeId: Int, variable: Int) {
113 mv.apply {
114 visitVarInsn(Opcodes.ALOAD, variable)
115 // Stack: mem
116 push(edgeId)
117 // Stack: mem | edgeId
118 visitInsn(Opcodes.DUP2)
119 // Stack: mem | edgeId | mem | edgeId
120 visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false)
121 // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in
122 // that case.
123 // This approach performs better than saturating the counter at 255 (see Section 3.3 of
124 // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf)
125 // Stack: mem | edgeId | counter (sign-extended to int)
126 push(0xff)
127 // Stack: mem | edgeId | counter (sign-extended to int) | 0x000000ff
128 visitInsn(Opcodes.IAND)
129 // Stack: mem | edgeId | counter (zero-extended to int)
130 push(1)
131 // Stack: mem | edgeId | counter | 1
132 visitInsn(Opcodes.IADD)
133 // Stack: mem | edgeId | counter + 1
134 visitInsn(Opcodes.DUP)
135 // Stack: mem | edgeId | counter + 1 | counter + 1
136 push(8)
137 // Stack: mem | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5)
138 visitInsn(Opcodes.ISHR)
139 // Stack: mem | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise
140 visitInsn(Opcodes.IADD)
141 // Stack: mem | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise
142 visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false)
143 // Stack: mem
144 visitInsn(Opcodes.POP)
145 if (isTesting) {
146 visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false)
147 }
148 }
149 }
150
151 // The remainder of this file interfaces with classes in org.jacoco.core.internal. Changes to this part should not be
152 // necessary unless JaCoCo is updated or the way we instrument for coverage changes fundamentally.
153
154 /**
155 * A [ProbeInserter] that injects the bytecode instrumentation returned by [instrumentControlFlowEdge] and modifies
156 * the stack size and number of local variables accordingly.
157 */
158 private inner class EdgeCoverageProbeInserter(
159 access: Int,
160 name: String,
161 desc: String,
162 mv: MethodVisitor,
163 arrayStrategy: IProbeArrayStrategy,
164 ) : ProbeInserter(access, name, desc, mv, arrayStrategy) {
165 override fun insertProbe(id: Int) {
166 instrumentControlFlowEdge(mv, id, variable)
167 }
168
169 override fun visitMaxs(maxStack: Int, maxLocals: Int) {
170 val newMaxStack = max(maxStack + instrumentControlFlowEdgeStackSize, loadCoverageMapStackSize)
171 mv.visitMaxs(newMaxStack, maxLocals + 1)
172 }
173 }
174
175 private val edgeCoverageProbeInserterFactory =
176 IProbeInserterFactory { access, name, desc, mv, arrayStrategy ->
177 EdgeCoverageProbeInserter(access, name, desc, mv, arrayStrategy)
178 }
179
180 private inner class EdgeCoverageClassProbesAdapter(cv: ClassProbesVisitor, trackFrames: Boolean) :
181 ClassProbesAdapter(cv, trackFrames) {
182 override fun nextId(): Int = nextEdgeId()
183 }
184
185 private val edgeCoverageClassProbesAdapterFactory = IClassProbesAdapterFactory { probesVisitor, trackFrames ->
186 EdgeCoverageClassProbesAdapter(probesVisitor, trackFrames)
187 }
188
189 private val edgeCoverageProbeArrayStrategy = object : IProbeArrayStrategy {
190 override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int {
191 loadCoverageMap(mv, variable)
192 return loadCoverageMapStackSize
193 }
194
195 override fun addMembers(cv: ClassVisitor, probeCount: Int) {}
196 }
197
198 private fun MethodVisitor.push(value: Int) {
199 InstrSupport.push(this, value)
200 }
201 }
202