@@ -30,6 +30,7 @@ import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.P
3030import org.objectweb.asm.ClassReader
3131import org.objectweb.asm.ClassVisitor
3232import org.objectweb.asm.ClassWriter
33+ import org.objectweb.asm.Label
3334import org.objectweb.asm.MethodVisitor
3435import java.lang.invoke.MethodHandle
3536import java.lang.invoke.MethodHandles.publicLookup
@@ -82,6 +83,22 @@ interface EdgeCoverageStrategy {
8283 val loadLocalVariableStackSize: Int
8384}
8485
86+ /* *
87+ * Per-edge source location data collected during instrumentation.
88+ *
89+ * @param sourceFile Qualified source path, e.g. "com/example/Foo.java"
90+ * @param methodNames Deduplicated method name table (SimpleClassName.method)
91+ * @param edgeData Flat array: [packedLine0, methodIdx0, packedLine1, methodIdx1, ...]
92+ * The sign bit of each packedLine encodes isFunctionEntry.
93+ */
94+ class EdgeLocationData (
95+ val sourceFile : String ,
96+ val methodNames : Array <String >,
97+ val edgeData : IntArray ,
98+ ) {
99+ override fun toString (): String = " EdgeLocationData(sourceFile=$sourceFile , methods=${methodNames.size} , edges=${edgeData.size / 2 } )"
100+ }
101+
85102// An instance of EdgeCoverageInstrumentor should only be used to instrument a single class as it
86103// internally tracks the edge IDs, which have to be globally unique.
87104class EdgeCoverageInstrumentor (
@@ -106,10 +123,38 @@ class EdgeCoverageInstrumentor(
106123 ),
107124 )
108125
126+ // ── Source location tracking ──────────────────────────────────
127+ private var sourceFile: String = " "
128+ private var simpleClassName: String = " "
129+ private val methodNameList = mutableListOf<String >()
130+ private val methodNameIndex = mutableMapOf<String , Int >()
131+ private val locationData = mutableListOf<Int >()
132+
133+ private fun internMethodName (name : String ): Int =
134+ methodNameIndex.getOrPut(name) {
135+ methodNameList.add(name)
136+ methodNameList.size - 1
137+ }
138+
139+ /* * Returns the collected edge locations, or null if no edges were instrumented. */
140+ fun buildEdgeLocations (): EdgeLocationData ? {
141+ if (numEdges == 0 ) return null
142+ return EdgeLocationData (
143+ sourceFile,
144+ methodNameList.toTypedArray(),
145+ locationData.toIntArray(),
146+ )
147+ }
148+
109149 override fun instrument (
110150 internalClassName : String ,
111151 bytecode : ByteArray ,
112152 ): ByteArray {
153+ val lastSlash = internalClassName.lastIndexOf(' /' )
154+ simpleClassName = if (lastSlash >= 0 ) internalClassName.substring(lastSlash + 1 ) else internalClassName
155+ // Fallback source file if the class has no SourceFile attribute.
156+ sourceFile = internalClassName
157+
113158 val reader = InstrSupport .classReaderFor(bytecode)
114159 val writer = ClassWriter (reader, 0 )
115160 val version = InstrSupport .getMajorVersion(reader)
@@ -144,6 +189,8 @@ class EdgeCoverageInstrumentor(
144189 /* *
145190 * A [ProbeInserter] that injects bytecode instrumentation at every control flow edge and
146191 * modifies the stack size and number of local variables accordingly.
192+ *
193+ * Also records the source location of each probe for PC symbolization.
147194 */
148195 private inner class EdgeCoverageProbeInserter (
149196 access : Int ,
@@ -152,7 +199,25 @@ class EdgeCoverageInstrumentor(
152199 mv : MethodVisitor ,
153200 arrayStrategy : IProbeArrayStrategy ,
154201 ) : ProbeInserter(access, name, desc, mv, arrayStrategy) {
202+ private var currentLine = 0
203+ private var isFirstProbe = true
204+ private val methodIdx = internMethodName(" $simpleClassName .$name " )
205+
206+ override fun visitLineNumber (
207+ line : Int ,
208+ start : Label ,
209+ ) {
210+ currentLine = line
211+ super .visitLineNumber(line, start)
212+ }
213+
155214 override fun insertProbe (id : Int ) {
215+ // Pack isFuncEntry into the sign bit of the line number.
216+ val packedLine = if (isFirstProbe) (currentLine or (1 shl 31 )) else currentLine
217+ locationData.add(packedLine)
218+ locationData.add(methodIdx)
219+ isFirstProbe = false
220+
156221 strategy.instrumentControlFlowEdge(mv, id, variable, coverageMapInternalClassName)
157222 }
158223
@@ -182,6 +247,20 @@ class EdgeCoverageInstrumentor(
182247 ) : ClassProbesAdapter(cpv, trackFrames) {
183248 override fun nextId (): Int = nextEdgeId()
184249
250+ override fun visitSource (
251+ source : String? ,
252+ debug : String? ,
253+ ) {
254+ if (source != null ) {
255+ // sourceFile was initialized to the internal class name (e.g. "com/example/Outer$Inner")
256+ // in instrument(). Extract the package prefix and prepend it to the declared source
257+ // file name, giving e.g. "com/example/Outer.java".
258+ val packageEnd = sourceFile.lastIndexOf(' /' )
259+ sourceFile = if (packageEnd >= 0 ) sourceFile.substring(0 , packageEnd + 1 ) + source else source
260+ }
261+ super .visitSource(source, debug)
262+ }
263+
185264 override fun visitEnd () {
186265 cpv.visitTotalProbeCount(numEdges)
187266 // Avoid calling super.visitEnd() as that invokes cpv.visitTotalProbeCount with an
0 commit comments