@@ -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,23 @@ 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 =
100+ " EdgeLocationData(sourceFile=$sourceFile , methods=${methodNames.size} , edges=${edgeData.size / 2 } )"
101+ }
102+
85103// An instance of EdgeCoverageInstrumentor should only be used to instrument a single class as it
86104// internally tracks the edge IDs, which have to be globally unique.
87105class EdgeCoverageInstrumentor (
@@ -106,10 +124,38 @@ class EdgeCoverageInstrumentor(
106124 ),
107125 )
108126
127+ // ── Source location tracking ──────────────────────────────────
128+ private var sourceFile: String = " "
129+ private var simpleClassName: String = " "
130+ private val methodNameList = mutableListOf<String >()
131+ private val methodNameIndex = mutableMapOf<String , Int >()
132+ private val locationData = mutableListOf<Int >()
133+
134+ private fun internMethodName (name : String ): Int =
135+ methodNameIndex.getOrPut(name) {
136+ methodNameList.add(name)
137+ methodNameList.size - 1
138+ }
139+
140+ /* * Returns the collected edge locations, or null if no edges were instrumented. */
141+ fun buildEdgeLocations (): EdgeLocationData ? {
142+ if (numEdges == 0 ) return null
143+ return EdgeLocationData (
144+ sourceFile,
145+ methodNameList.toTypedArray(),
146+ locationData.toIntArray(),
147+ )
148+ }
149+
109150 override fun instrument (
110151 internalClassName : String ,
111152 bytecode : ByteArray ,
112153 ): ByteArray {
154+ val lastSlash = internalClassName.lastIndexOf(' /' )
155+ simpleClassName = if (lastSlash >= 0 ) internalClassName.substring(lastSlash + 1 ) else internalClassName
156+ // Fallback source file if the class has no SourceFile attribute.
157+ sourceFile = internalClassName
158+
113159 val reader = InstrSupport .classReaderFor(bytecode)
114160 val writer = ClassWriter (reader, 0 )
115161 val version = InstrSupport .getMajorVersion(reader)
@@ -144,6 +190,8 @@ class EdgeCoverageInstrumentor(
144190 /* *
145191 * A [ProbeInserter] that injects bytecode instrumentation at every control flow edge and
146192 * modifies the stack size and number of local variables accordingly.
193+ *
194+ * Also records the source location of each probe for PC symbolization.
147195 */
148196 private inner class EdgeCoverageProbeInserter (
149197 access : Int ,
@@ -152,7 +200,22 @@ class EdgeCoverageInstrumentor(
152200 mv : MethodVisitor ,
153201 arrayStrategy : IProbeArrayStrategy ,
154202 ) : ProbeInserter(access, name, desc, mv, arrayStrategy) {
203+ private var currentLine = 0
204+ private var isFirstProbe = true
205+ private val methodIdx = internMethodName(" $simpleClassName .$name " )
206+
207+ override fun visitLineNumber (line : Int , start : Label ) {
208+ currentLine = line
209+ super .visitLineNumber(line, start)
210+ }
211+
155212 override fun insertProbe (id : Int ) {
213+ // Pack isFuncEntry into the sign bit of the line number.
214+ val packedLine = if (isFirstProbe) (currentLine or (1 shl 31 )) else currentLine
215+ locationData.add(packedLine)
216+ locationData.add(methodIdx)
217+ isFirstProbe = false
218+
156219 strategy.instrumentControlFlowEdge(mv, id, variable, coverageMapInternalClassName)
157220 }
158221
@@ -182,6 +245,17 @@ class EdgeCoverageInstrumentor(
182245 ) : ClassProbesAdapter(cpv, trackFrames) {
183246 override fun nextId (): Int = nextEdgeId()
184247
248+ override fun visitSource (source : String? , debug : String? ) {
249+ if (source != null ) {
250+ // sourceFile was initialized to the internal class name (e.g. "com/example/Outer$Inner")
251+ // in instrument(). Extract the package prefix and prepend it to the declared source
252+ // file name, giving e.g. "com/example/Outer.java".
253+ val packageEnd = sourceFile.lastIndexOf(' /' )
254+ sourceFile = if (packageEnd >= 0 ) sourceFile.substring(0 , packageEnd + 1 ) + source else source
255+ }
256+ super .visitSource(source, debug)
257+ }
258+
185259 override fun visitEnd () {
186260 cpv.visitTotalProbeCount(numEdges)
187261 // Avoid calling super.visitEnd() as that invokes cpv.visitTotalProbeCount with an
0 commit comments