|
|
|
@ -107,7 +107,7 @@ namespace ICSharpCode.CodeCoverage |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<CodeCoverageBranchPoint> GetBranchPoints() { |
|
|
|
List<CodeCoverageBranchPoint> GetBranchPoints() { |
|
|
|
// get all SequencePoints
|
|
|
|
// get all BranchPoints
|
|
|
|
List<CodeCoverageBranchPoint> bps = new List<CodeCoverageBranchPoint>(); |
|
|
|
List<CodeCoverageBranchPoint> bps = new List<CodeCoverageBranchPoint>(); |
|
|
|
var xBPoints = this.element |
|
|
|
var xBPoints = this.element |
|
|
|
.Elements("BranchPoints") |
|
|
|
.Elements("BranchPoints") |
|
|
|
@ -130,108 +130,94 @@ namespace ICSharpCode.CodeCoverage |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// start & final method sequence points line and offset
|
|
|
|
// Find method-body first SequencePoint "{"
|
|
|
|
int methodStartLine = 0; |
|
|
|
// and then first next sequencePoint (and offset of that instruction in body)
|
|
|
|
int methodStartOffset = 0; |
|
|
|
// This will be used to skip CCRewrite(n) BranchPoint's (Requires)
|
|
|
|
|
|
|
|
CodeCoverageSequencePoint startSeqPoint = null; |
|
|
|
int methodFinalLine = 0; |
|
|
|
|
|
|
|
int methodFinalOffset = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// find start & final method sequence points and offsets
|
|
|
|
|
|
|
|
bool nextMatch = false; |
|
|
|
|
|
|
|
int startLine = 0; |
|
|
|
|
|
|
|
int finalLine = 0; |
|
|
|
|
|
|
|
int startChar = 0; |
|
|
|
|
|
|
|
int finalChar = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Find method body SequencePoint ({)
|
|
|
|
|
|
|
|
// and then first next sequencePoint (and offset of first instruction in body)
|
|
|
|
|
|
|
|
// This will skip Contract.Require SequencePoints
|
|
|
|
|
|
|
|
// and help to filter-out Contract.Require BranchPoint's
|
|
|
|
|
|
|
|
foreach (CodeCoverageSequencePoint sp in this.SequencePoints) { |
|
|
|
foreach (CodeCoverageSequencePoint sp in this.SequencePoints) { |
|
|
|
startLine = sp.Line; |
|
|
|
if ( sp.Line == sp.EndLine && ((sp.EndColumn-sp.Column) == 1) ) { |
|
|
|
finalLine = sp.EndLine; |
|
|
|
startSeqPoint = sp; |
|
|
|
startChar = sp.Column; |
|
|
|
|
|
|
|
finalChar = sp.EndColumn; |
|
|
|
|
|
|
|
if ( nextMatch ) { |
|
|
|
|
|
|
|
methodStartLine = startLine; |
|
|
|
|
|
|
|
methodStartOffset = sp.Offset; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if ( startLine==finalLine && (finalChar-startChar)==1 ) { |
|
|
|
|
|
|
|
nextMatch = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Debug.Assert (!Object.ReferenceEquals(null, startSeqPoint)); |
|
|
|
|
|
|
|
if (Object.ReferenceEquals(null, startSeqPoint)) { return null; } |
|
|
|
|
|
|
|
|
|
|
|
// find method body last SequencePoint and final offset (})
|
|
|
|
// Find method-body last SequencePoint "}" and offset
|
|
|
|
// This will skip Contract.Ensures SequencePoints
|
|
|
|
// This will be used to skip CCRewrite(n) BranchPoint's (Ensures)
|
|
|
|
// and help to filter-out Contract.Ensures BranchPoint's
|
|
|
|
CodeCoverageSequencePoint finalSeqPoint = null; |
|
|
|
foreach (CodeCoverageSequencePoint sp in Enumerable.Reverse(this.SequencePoints)) { |
|
|
|
foreach (CodeCoverageSequencePoint sp in Enumerable.Reverse(this.SequencePoints)) { |
|
|
|
startLine = sp.Line; |
|
|
|
if ( sp.Line == sp.EndLine && ((sp.EndColumn-sp.Column) == 1) ) { |
|
|
|
finalLine = sp.EndLine; |
|
|
|
finalSeqPoint = sp; |
|
|
|
startChar = sp.Column; |
|
|
|
|
|
|
|
finalChar = sp.EndColumn; |
|
|
|
|
|
|
|
if ( startLine==finalLine && (finalChar-startChar)==1 ) { |
|
|
|
|
|
|
|
methodFinalLine = finalLine; |
|
|
|
|
|
|
|
methodFinalOffset = sp.Offset; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Debug.Assert ( !Object.ReferenceEquals( null, finalSeqPoint) ); |
|
|
|
|
|
|
|
if (Object.ReferenceEquals(null, finalSeqPoint)) { return null; } |
|
|
|
|
|
|
|
|
|
|
|
// Find compiler inserted code (=> inserted hidden branches)
|
|
|
|
// Find compiler inserted code (=> inserted hidden branches)
|
|
|
|
// SequencePoints are ordered by offset and that order exposes reverse ordered lines
|
|
|
|
// SequencePoints are ordered by offset and that order exposes reverse ordered lines
|
|
|
|
// ie: In code: foreach ( var item in items )
|
|
|
|
// ie: foreach ( var item in (IEnumerable)items ) code for "in" keyword is generated "out-of-sequence",
|
|
|
|
// Keyword "in" code is generated after loop with lower line number than previous line (reversed line order).
|
|
|
|
// with lower line number than previous line (reversed line order).
|
|
|
|
// That "in" code contains hidden branches that we
|
|
|
|
// Generated "in" code for IEnumerables contains hidden "try/catch/finally" branches that
|
|
|
|
// do not want or cannot cover by test-case because is handled by earlier line
|
|
|
|
// we do not want or cannot cover by test-case because is handled in earlier code (SequencePoint)
|
|
|
|
// ie: Possible NullReferenceException in foreach loop is pre-handled at method entry, ie. by Contract.Require(items!=null)
|
|
|
|
// ie: NullReferenceException in foreach loop is pre-handled at method entry, ie. by Contract.Require(items!=null)
|
|
|
|
nextMatch = false; |
|
|
|
bool nextMatch = false; |
|
|
|
int lastOffset = 0; |
|
|
|
CodeCoverageSequencePoint previousSeqPoint = startSeqPoint; |
|
|
|
int lastLine = methodStartLine; |
|
|
|
List<Tuple<int,int>> excludeOffsetList = new List<Tuple<int, int>>(); |
|
|
|
List<Tuple<int,int>> excludeList = new List<Tuple<int, int>>(); |
|
|
|
foreach (CodeCoverageSequencePoint currentSeqPoint in this.SequencePoints) { |
|
|
|
foreach (CodeCoverageSequencePoint sp in this.SequencePoints) { |
|
|
|
|
|
|
|
if ( (sp.Line < methodStartLine) || methodFinalLine < sp.Line ) { continue ; } |
|
|
|
// ignore CCRewrite(n) contracts
|
|
|
|
|
|
|
|
if (currentSeqPoint.Offset < startSeqPoint.Offset) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (currentSeqPoint.Offset > finalSeqPoint.Offset) |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
if (nextMatch && (sp.Line > lastLine)) { |
|
|
|
if (nextMatch) { |
|
|
|
nextMatch = false; |
|
|
|
nextMatch = false; |
|
|
|
excludeList.Add(new Tuple<int, int> ( lastOffset , sp.Offset )); |
|
|
|
excludeOffsetList.Add(new Tuple<int, int> ( previousSeqPoint.Offset , currentSeqPoint.Offset )); |
|
|
|
} |
|
|
|
} |
|
|
|
// reversed line number?
|
|
|
|
// current SP has lower line number than stored SP and is made of two characters?
|
|
|
|
if (!nextMatch && (sp.Line < lastLine)) { |
|
|
|
// Very likely to be only "in" keyword (as observed in coverage.xml)
|
|
|
|
|
|
|
|
if (currentSeqPoint.Line < previousSeqPoint.Line |
|
|
|
|
|
|
|
&& currentSeqPoint.Line == currentSeqPoint.EndLine |
|
|
|
|
|
|
|
&& (currentSeqPoint.EndColumn - currentSeqPoint.Column) == 2 ) { |
|
|
|
nextMatch = true; |
|
|
|
nextMatch = true; |
|
|
|
lastOffset = sp.Offset; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
lastLine = sp.Line; |
|
|
|
previousSeqPoint = currentSeqPoint; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Collect branch offsets within method boundary { } (exclude Contracts)
|
|
|
|
// Collect & offset-merge BranchPoints within method boundary { }
|
|
|
|
// and filter with excludeList
|
|
|
|
// => exclude CCRewrite(n) Contracts and filter with excludeOffsetList
|
|
|
|
Dictionary<int, branchOffset> branchDictionary = new Dictionary<int, branchOffset>(); |
|
|
|
Dictionary<int, branchOffset> branchDictionary = new Dictionary<int, branchOffset>(); |
|
|
|
foreach (CodeCoverageBranchPoint bp in this.BranchPoints) { |
|
|
|
foreach (CodeCoverageBranchPoint bp in this.BranchPoints) { |
|
|
|
|
|
|
|
|
|
|
|
if ( methodStartOffset <= bp.Offset && bp.Offset <= methodFinalOffset ) { |
|
|
|
// exclude CCRewrite(n) contracts
|
|
|
|
|
|
|
|
if (bp.Offset < startSeqPoint.Offset) |
|
|
|
// Apply exclude BranchPoint filter
|
|
|
|
continue; |
|
|
|
nextMatch = true; |
|
|
|
if (bp.Offset > finalSeqPoint.Offset) |
|
|
|
foreach (var range in excludeList) { |
|
|
|
break; |
|
|
|
if (range.Item1 < bp.Offset && bp.Offset < range.Item2) { |
|
|
|
|
|
|
|
// exclude range match
|
|
|
|
|
|
|
|
nextMatch = false; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} if (!nextMatch) { continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// update/insert branch offset coverage data
|
|
|
|
// Apply excludeOffset filter
|
|
|
|
if ( branchDictionary.ContainsKey( bp.Offset ) ) { |
|
|
|
nextMatch = true; |
|
|
|
branchOffset update = branchDictionary[bp.Offset]; |
|
|
|
foreach (var offsetRange in excludeOffsetList) { |
|
|
|
update.Visit += bp.VisitCount!=0?1:0; |
|
|
|
if (offsetRange.Item1 < bp.Offset && bp.Offset < offsetRange.Item2) { |
|
|
|
update.Count += 1; |
|
|
|
// exclude range match
|
|
|
|
} else { |
|
|
|
nextMatch = false; break; |
|
|
|
// Insert first branch at offset
|
|
|
|
|
|
|
|
branchOffset insert = new branchOffset(bp.Offset); |
|
|
|
|
|
|
|
insert.Visit = bp.VisitCount!=0?1:0; |
|
|
|
|
|
|
|
insert.Count = 1; |
|
|
|
|
|
|
|
branchDictionary[insert.Offset] = insert; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} if (!nextMatch) { continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// update/insert branch offset coverage data
|
|
|
|
|
|
|
|
if ( branchDictionary.ContainsKey( bp.Offset ) ) { |
|
|
|
|
|
|
|
branchOffset update = branchDictionary[bp.Offset]; |
|
|
|
|
|
|
|
update.Visit += bp.VisitCount!=0?1:0; |
|
|
|
|
|
|
|
update.Count += 1; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Insert first branch at offset
|
|
|
|
|
|
|
|
branchOffset insert = new branchOffset(bp.Offset); |
|
|
|
|
|
|
|
insert.Visit = bp.VisitCount!=0?1:0; |
|
|
|
|
|
|
|
insert.Count = 1; |
|
|
|
|
|
|
|
branchDictionary[insert.Offset] = insert; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|