• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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