Skip to content

Commit 801c8ec

Browse files
cmd/cover: exclude commented-out code from coverage instrumentation
1 parent 2b62144 commit 801c8ec

File tree

2 files changed

+486
-2
lines changed

2 files changed

+486
-2
lines changed

src/cmd/cover/cover.go

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,197 @@ type File struct {
266266
pkg *Package
267267
}
268268

269+
// BlockSegment represents a segment of a basic block that can be either
270+
// executable code or commented-out code.
271+
type BlockSegment struct {
272+
start token.Pos
273+
end token.Pos
274+
hasCode bool // true if this segment contains executable code
275+
}
276+
277+
// blockSplitter holds the state for splitting a block by comments.
278+
// It tracks cursor position, line state, and section boundaries
279+
// while scanning through source code character by character.
280+
type blockSplitter struct {
281+
// Source information
282+
file *token.File
283+
startOffset int
284+
285+
// Accumulated results
286+
segments []BlockSegment
287+
288+
// Cursor position
289+
i int
290+
indexStartCurrentLine int
291+
currentOffset int
292+
currentSegmentStart token.Pos
293+
294+
// Section state (persists across lines)
295+
inCommentedSection bool
296+
297+
// Line state (reset each line)
298+
lineHasCode bool
299+
lineHasComment bool
300+
301+
// Cursor state (tracks what we're inside)
302+
inSingleLineComment bool
303+
inMultiLineComment bool
304+
inString bool
305+
inRawString bool
306+
}
307+
308+
// processLine handles end-of-line processing: computes state transitions
309+
// and decides whether to create a new segment based on code/comment boundaries.
310+
func (bs *blockSplitter) processLine() {
311+
lineStart := bs.currentOffset
312+
lineEnd := bs.currentOffset + (bs.i - bs.indexStartCurrentLine)
313+
314+
if bs.inCommentedSection && bs.lineHasCode {
315+
// End of commented section, start of code section
316+
segmentEnd := bs.file.Pos(lineStart)
317+
bs.segments = append(bs.segments, BlockSegment{
318+
start: bs.currentSegmentStart,
319+
end: segmentEnd,
320+
hasCode: false,
321+
})
322+
bs.currentSegmentStart = bs.file.Pos(lineStart)
323+
bs.inCommentedSection = false
324+
} else if !bs.inCommentedSection && !bs.lineHasCode && bs.lineHasComment {
325+
// End of code section, start of commented section
326+
segmentEnd := bs.file.Pos(lineStart)
327+
if bs.currentSegmentStart < segmentEnd {
328+
bs.segments = append(bs.segments, BlockSegment{
329+
start: bs.currentSegmentStart,
330+
end: segmentEnd,
331+
hasCode: true,
332+
})
333+
}
334+
bs.currentSegmentStart = bs.file.Pos(lineStart)
335+
bs.inCommentedSection = true
336+
}
337+
338+
bs.currentOffset = lineEnd
339+
}
340+
341+
// resetLineState resets line-specific state for a new line.
342+
func (bs *blockSplitter) resetLineState() {
343+
bs.indexStartCurrentLine = bs.i
344+
bs.lineHasComment = bs.inMultiLineComment
345+
bs.lineHasCode = false
346+
bs.inSingleLineComment = false
347+
}
348+
349+
// inStringOrComment returns true if currently inside a string or comment.
350+
func (bs *blockSplitter) inStringOrComment() bool {
351+
return bs.inString || bs.inRawString || bs.inSingleLineComment || bs.inMultiLineComment
352+
}
353+
354+
// splitBlockByComments analyzes a block range and splits it into segments,
355+
// separating executable code from any commented lines.
356+
// To do this, it reads character by character the original source code.
357+
func (f *File) splitBlockByComments(start, end token.Pos) []BlockSegment {
358+
startOffset := f.offset(start)
359+
endOffset := f.offset(end)
360+
361+
if startOffset >= endOffset || endOffset > len(f.content) {
362+
return []BlockSegment{{start: start, end: end, hasCode: true}}
363+
}
364+
365+
originalSourceCode := f.content[startOffset:endOffset]
366+
367+
bs := &blockSplitter{
368+
file: f.fset.File(start),
369+
startOffset: startOffset,
370+
currentOffset: startOffset,
371+
currentSegmentStart: start,
372+
}
373+
374+
for bs.i < len(originalSourceCode) {
375+
char := originalSourceCode[bs.i]
376+
377+
if char == '\\' && bs.inString && bs.i+1 < len(originalSourceCode) {
378+
bs.lineHasCode = true
379+
bs.i += 2 // Skip escaped character
380+
continue
381+
}
382+
383+
if char == '"' && !bs.inRawString && !bs.inSingleLineComment && !bs.inMultiLineComment {
384+
bs.lineHasCode = true
385+
bs.inString = !bs.inString
386+
bs.i++
387+
continue
388+
}
389+
390+
if char == '`' && !bs.inString && !bs.inSingleLineComment && !bs.inMultiLineComment {
391+
bs.lineHasCode = true
392+
bs.inRawString = !bs.inRawString
393+
bs.i++
394+
continue
395+
}
396+
397+
if char == '\n' {
398+
bs.i++
399+
bs.processLine()
400+
bs.resetLineState()
401+
continue
402+
}
403+
404+
if bs.i+1 < len(originalSourceCode) {
405+
nextChar := originalSourceCode[bs.i+1]
406+
if char == '/' && nextChar == '/' && !bs.inString && !bs.inRawString && !bs.inMultiLineComment {
407+
bs.inSingleLineComment = true
408+
bs.lineHasComment = true
409+
bs.i += 2
410+
continue
411+
}
412+
413+
if char == '/' && nextChar == '*' && !bs.inString && !bs.inRawString && !bs.inSingleLineComment {
414+
bs.inMultiLineComment = true
415+
bs.lineHasComment = true
416+
bs.i += 2
417+
continue
418+
}
419+
420+
if char == '*' && nextChar == '/' && bs.inMultiLineComment {
421+
bs.inMultiLineComment = false
422+
bs.lineHasComment = true
423+
bs.i += 2
424+
continue
425+
}
426+
}
427+
428+
// If we matched nothing else and the char is not a whitespace, we are in normal code.
429+
if !bs.lineHasCode && !isWhitespace(char) && !bs.inSingleLineComment && !bs.inMultiLineComment {
430+
bs.lineHasCode = true
431+
}
432+
433+
bs.i++
434+
}
435+
436+
// Process the last line if it doesn't end with a newline
437+
bs.processLine()
438+
439+
// Add the final segment
440+
if bs.currentSegmentStart < end {
441+
bs.segments = append(bs.segments, BlockSegment{
442+
start: bs.currentSegmentStart,
443+
end: end,
444+
hasCode: !bs.inCommentedSection,
445+
})
446+
}
447+
448+
// If no segments were created, return the original block as a code segment
449+
if len(bs.segments) == 0 {
450+
return []BlockSegment{{start: start, end: end, hasCode: true}}
451+
}
452+
453+
return bs.segments
454+
}
455+
456+
func isWhitespace(b byte) bool {
457+
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
458+
}
459+
269460
// findText finds text in the original source, starting at pos.
270461
// It correctly skips over comments and assumes it need not
271462
// handle quoted strings.
@@ -755,7 +946,14 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
755946
// Special case: make sure we add a counter to an empty block. Can't do this below
756947
// or we will add a counter to an empty statement list after, say, a return statement.
757948
if len(list) == 0 {
758-
f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
949+
// Split the empty block by comments and only add counter if there's executable content
950+
segments := f.splitBlockByComments(insertPos, blockEnd)
951+
for _, segment := range segments {
952+
if segment.hasCode {
953+
f.edit.Insert(f.offset(segment.start), f.newCounter(segment.start, segment.end, 0)+";")
954+
break // Only insert one counter per basic block
955+
}
956+
}
759957
return
760958
}
761959
// Make a copy of the list, as we may mutate it and should leave the
@@ -805,7 +1003,20 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
8051003
end = blockEnd
8061004
}
8071005
if pos != end { // Can have no source to cover if e.g. blocks abut.
808-
f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
1006+
// Split the block by comments and create counters only for executable segments
1007+
segments := f.splitBlockByComments(pos, end)
1008+
for i, segment := range segments {
1009+
if segment.hasCode {
1010+
// Only create counter for executable code segments
1011+
// Insert counter at the beginning of the executable segment
1012+
insertOffset := f.offset(segment.start)
1013+
// For the first segment, use the original insertPos if it's before the segment
1014+
if i == 0 {
1015+
insertOffset = f.offset(insertPos)
1016+
}
1017+
f.edit.Insert(insertOffset, f.newCounter(segment.start, segment.end, last)+";")
1018+
}
1019+
}
8091020
}
8101021
list = list[last:]
8111022
if len(list) == 0 {

0 commit comments

Comments
 (0)