For really cluttered files like https://github.com/StefanSalewski/gintro/blob/master/tests/gen.nim it is not easy to find code segments which are never executed. In the past I added 'echo' or 'assert false' statements when I assumed that branches are never executed. Yesterday I tried to create a macro for this:
# module activeLines
import macros, algorithm, strformat
type
SortObj = object
line, cnt: int
var
lineCT {.compileTime.}: seq[int]
line: seq[int]
ec = newSeq[int](1024 * 8) # we may use a hash table?
macro excnt*(): untyped =
let ll = lineinfoObj(result).line
lineCT.add(ll) # add the entry when macro is instantiated
result = quote do:
while line.len < `lineCT`.len: # copy CT seq entries to runtime seq
line.add(`lineCT`[line.len])
let (file, line, col) = instantiationInfo() # works at runtime, but is not really needed
assert line == `ll`
if ec.len <= line:
ec.setLen(line + 1)
ec[line] += 1
proc excntResult* =
var s = newSeq[SortObj](line.len)
for i in 0 .. s.high:
s[i].line = line[i]
s[i].cnt = ec[line[i]]
s.sort(proc (x, y: SortObj): int = cmp((x.cnt, x.line), (y.cnt, y.line)))
echo "line : activity"
template print = echo fmt"{s[i].line:>5}", ": ", s[i].cnt
if s.len < 11:
for i in 0 .. s.high:
print
else:
for i in 0 .. 4:
print
echo ".."
for i in s.high - 4 .. s.high:
print
when isMainModule:
proc main =
excnt()
var sum: int
excnt()
for i in 0 .. 9:
excnt()
sum += i
excnt()
if sum < 0:
sum = 0; excnt()
if sum > 50:
sum = 50; excnt()
if sum mod 7 == 7:
sum = 13; excnt()
if 1 > 2:
excnt()
var a, b: int
if a > -1:
excnt()
if a + b > 0: # to get output for this last always false statement we have to add one additional excnt()
excnt()
echo "a + b"
excnt() # additional dummy one
main()
excntResult()
$ nim c --gc:arc -r activeLines/activeLines.nim
line : activity
54: 0
56: 0
58: 0
60: 0
65: 0
..
48: 1
63: 1
67: 1
50: 10
52: 10
Seems to be not that bad. Can we improve it? The copying of the compile time seq into the runtime seq line.add(`lineCT`[line.len]) is a bit strange, but I have found no other way. Also a bit ugly is that to see the last never executed line we have to add on more executed macro call.
You inspired me to create a macro that inserts all of this stuff automatically! It's called mort and you can view it here: https://github.com/jyapayne/mort
As a quick example, you can do this:
import strutils
import mort
proc mysnakeToCamel(s: cstring): string {.findDeadCode.} =
var i, j: int
result = newString(s.len)
if s[i] == '_': inc(i)
while true:
if s[i] == '_' and (s[i + 1] == '\0' or s[i + 1] == '_'):
inc(i)
elif (s[i] == '_' or s[i] == '-') and s[i + 1].isLowerAscii and not s[i - 1].isUpperAscii:
inc(i)
result[j] = toUpperAscii(s[i])
inc(i)
inc(j)
else:
result[j] = s[i]
inc(i)
inc(j)
if s[i] == '\0':
result.setLen(j)
break
if result[0] == '\0': # this may result, so we emit a dummy name as marker
result = "QQQ"
proc main() =
discard mySnakeToCamel("diff_is_good")
discard mySnakeToCamel("_diff_is_good")
printCodeUsage()
printDeadLines()
main()
Output:
6: 2
8: 1
10: 20
11: 0
13: 4
18: 16
22: 2
25: 0
Dead code found in /Users/joey/Projects/mort/tests/testmort.nim
at line 11
Dead code found in /Users/joey/Projects/mort/tests/testmort.nim
at line 25