1 /**
2  * Implementation of code coverage analyzer.
3  *
4  * Copyright: Copyright Digital Mars 1995 - 2015.
5  * License: Distributed under the
6  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
7  *    (See accompanying file LICENSE)
8  * Authors:   Walter Bright, Sean Kelly
9  * Source: $(DRUNTIMESRC rt/_cover.d)
10  */
11 
12 module rt.cover;
13 
14 import core.internal.util.math : min, max;
15 
16 private
17 {
18     version (Windows)
19     {
20         import core.sys.windows.basetsd /+: HANDLE+/;
21         import core.sys.windows.winbase /+: LOCKFILE_EXCLUSIVE_LOCK, LockFileEx, OVERLAPPED, SetEndOfFile+/;
22     }
23     else version (Posix)
24     {
25         import core.sys.posix.fcntl;
26         import core.sys.posix.unistd;
27     }
28     import core.stdc.config : c_long;
29     import core.stdc.stdio;
30     import core.stdc.stdlib;
31     import core.internal.utf;
32 
33     struct BitArray
34     {
35         size_t  len;
36         size_t* ptr;
37 
38         bool opIndex( size_t i )
39         in
40         {
41             assert( i < len );
42         }
43         do
44         {
45             static if (size_t.sizeof == 8)
46                 return ((ptr[i >> 6] & (1L << (i & 63)))) != 0;
47             else static if (size_t.sizeof == 4)
48                 return ((ptr[i >> 5] & (1  << (i & 31)))) != 0;
49             else
50                 static assert(0);
51         }
52     }
53 
54     struct Cover                // one of these for each module being analyzed
55     {
56         string      filename;
57         BitArray    valid;      // bit array of which source lines are executable code lines
58         uint[]      data;       // array of line execution counts
59         ubyte       minPercent; // minimum percentage coverage required
60     }
61 
62     __gshared
63     {
64         Cover[] gdata;
65         Config config;
66     }
67 
68     struct Config
69     {
70         string  srcpath;
71         string  dstpath;
72         bool    merge;
73 
74     @nogc nothrow:
75 
76         bool initialize()
77         {
78             import core.internal.parseoptions : initConfigOptions;
79             return initConfigOptions(this, this.errorName);
80         }
81 
82         void help()
83         {
84             string s = "Code coverage options are specified as whitespace separated assignments:
85     merge:0|1      - 0 overwrites existing reports, 1 merges current run with existing coverage reports (default: %d)
86     dstpath:<PATH> - writes code coverage reports to <PATH> (default: current
87             working directory)
88     srcpath:<PATH> - sets the path where the source files are located to <PATH>
89     (default: current working directory)
90 ";
91             printf(s.ptr, merge);
92         }
93 
94         string errorName() { return "covopt"; }
95     }
96 }
97 
98 
99 /**
100  * Set path to where source files are located.
101  *
102  * Params:
103  *  pathname = The new path name.
104  */
105 extern (C) void dmd_coverSourcePath( string pathname )
106 {
107     config.srcpath = pathname;
108 }
109 
110 
111 /**
112  * Set path to where listing files are to be written.
113  *
114  * Params:
115  *  pathname = The new path name.
116  */
117 extern (C) void dmd_coverDestPath( string pathname )
118 {
119     config.dstpath = pathname;
120 }
121 
122 
123 /**
124  * Set merge mode.
125  *
126  * Params:
127  *      flag = true means new data is summed with existing data in the listing
128  *         file; false means a new listing file is always created.
129  */
130 extern (C) void dmd_coverSetMerge( bool flag )
131 {
132     config.merge = flag;
133 }
134 
135 
136 /**
137  * The coverage callback.
138  *
139  * Params:
140  *  filename = The name of the coverage file.
141  *  valid    = Bit array containing the valid code lines for coverage
142  *  data     = Array containg the coverage hits of each line
143  *  minPercent = minimal coverage of the module
144  */
145 extern (C) void _d_cover_register2(string filename, size_t[] valid, uint[] data, ubyte minPercent)
146 {
147     assert(minPercent <= 100);
148 
149     Cover c;
150 
151     c.filename  = filename;
152     c.valid.ptr = valid.ptr;
153     c.valid.len = valid.length;
154     c.data      = data;
155     c.minPercent = minPercent;
156     gdata      ~= c;
157 }
158 
159 /* Kept for the moment for backwards compatibility.
160  */
161 extern (C) void _d_cover_register( string filename, size_t[] valid, uint[] data )
162 {
163     _d_cover_register2(filename, valid, data, 0);
164 }
165 
166 private:
167 
168 // returns 0 if s isn't a number
169 uint parseNum(const(char)[] s)
170 {
171     while (s.length && s[0] == ' ')
172         s = s[1 .. $];
173     uint res;
174     while (s.length && s[0] >= '0' && s[0] <= '9')
175     {
176         res = 10 * res + s[0] - '0';
177         s = s[1 .. $];
178     }
179     return res;
180 }
181 
182 const(char)[] parseContent(const(char)[] s)
183 {
184     while (s.length && s[0] != '|')
185         s = s[1 .. $];
186     return s[1 .. $];
187 }
188 
189 bool lstEquals(char[][] sourceLines, char[][] lstLines)
190 {
191     if (sourceLines.length != lstLines.length - 1U)
192         return false;
193 
194     foreach (i, line; sourceLines)
195     {
196         auto content = parseContent(lstLines[i]);
197         // length mismatch
198         if (line.length != content.length) return false;
199 
200         // char content mismatch
201         foreach (j, c; content)
202             if (line[j] != c) return false;
203     }
204 
205     return true;
206 }
207 
208 unittest
209 {
210     char[][] src = cast(char[][])[ "12345", " | 12345, asasd", "|", ".;" ];
211     char[][] lst = cast(char[][])[ "       |12345", "       | | 12345, asasd", "      1||", "0000000|.;", "" ];
212     char[][] badLst = cast(char[][])[ "       |12344", "       | | 12345, asasd", "      1||", "0000000|.;", "" ];
213     assert(lstEquals(src, lst));
214     assert(!lstEquals(src, []));
215     assert(!lstEquals(src, badLst));
216 }
217 
218 shared static this()
219 {
220     config.initialize();
221 }
222 
223 shared static ~this()
224 {
225     if (!gdata.length) return;
226 
227     const NUMLINES = 16384 - 1;
228     const NUMCHARS = 16384 * 16 - 1;
229 
230     auto buf = new char[NUMCHARS];
231     auto lines = new char[][NUMLINES];
232     auto lstLines = new char[][NUMLINES];
233 
234     foreach (c; gdata)
235     {
236         auto fname = appendFN(config.dstpath, addExt(baseName(c.filename), "lst"));
237         auto flst = openOrCreateFile(fname);
238         if (flst is null)
239             continue;
240         lockFile(fileno(flst)); // gets unlocked by fclose
241         scope(exit) fclose(flst);
242 
243         if (!readFile(appendFN(config.srcpath, c.filename), buf))
244             continue;
245         splitLines(buf, lines);
246 
247         // Calculate the minimum line length between the source file and c.data
248         auto minLineLength = min(c.data.length, lines.length);
249 
250         foreach (i; 0 .. minLineLength)
251             lines[i] = expandTabs(lines[i]);
252 
253         auto buf2 = new char[NUMCHARS];
254         if (config.merge && readFile(flst, buf2))
255         {
256             splitLines(buf2, lstLines);
257 
258             // check if source is the same before merge
259             if (lstEquals(lines, lstLines))
260                 foreach (i, line; lstLines[0 .. min($, c.data.length)])
261                     c.data[i] += parseNum(line);
262         }
263 
264         // Calculate the maximum number of digits in the line with the greatest
265         // number of calls.
266         uint maxCallCount;
267         foreach (n; c.data[0 .. minLineLength])
268             maxCallCount = max(maxCallCount, n);
269 
270         // Make sure that there are a minimum of seven columns in each file so
271         // that unless there are a very large number of calls, the columns in
272         // each files lineup.
273         immutable maxDigits = max(7, digits(maxCallCount));
274 
275         uint nno;
276         uint nyes;
277 
278         // rewind for overwriting
279         fseek(flst, 0, SEEK_SET);
280 
281         foreach (i, n; c.data[0 .. minLineLength])
282         {
283             auto line = lines[i];
284 
285             if (n == 0)
286             {
287                 if (c.valid[i])
288                 {
289                     ++nno;
290                     fprintf(flst, "%0*u|%.*s\n", maxDigits, 0, cast(int)line.length, line.ptr);
291                 }
292                 else
293                 {
294                     fprintf(flst, "%*s|%.*s\n", maxDigits, " ".ptr, cast(int)line.length, line.ptr);
295                 }
296             }
297             else
298             {
299                 ++nyes;
300                 fprintf(flst, "%*u|%.*s\n", maxDigits, n, cast(int)line.length, line.ptr);
301             }
302         }
303 
304         if (nyes + nno) // no divide by 0 bugs
305         {
306             uint percent = ( nyes * 100 ) / ( nyes + nno );
307             fprintf(flst, "%.*s is %d%% covered\n", cast(int)c.filename.length, c.filename.ptr, percent);
308             if (percent < c.minPercent)
309             {
310                 fprintf(stderr, "Error: %.*s is %d%% covered, less than required %d%%\n",
311                     cast(int)c.filename.length, c.filename.ptr, percent, c.minPercent);
312                 exit(EXIT_FAILURE);
313             }
314         }
315         else
316         {
317             fprintf(flst, "%.*s has no code\n", cast(int)c.filename.length, c.filename.ptr);
318         }
319 
320         version (Windows)
321             SetEndOfFile(handle(fileno(flst)));
322         else
323             ftruncate(fileno(flst), ftell(flst));
324     }
325 }
326 
327 uint digits(uint number)
328 {
329     import core.stdc.math;
330     return number ? cast(uint)floor(log10(number)) + 1 : 1;
331 }
332 
333 unittest
334 {
335     static void testDigits(uint num, uint dgts)
336     {
337         assert(digits(num) == dgts);
338         assert(digits(num - 1) == dgts - 1);
339         assert(digits(num + 1) == dgts);
340     }
341     assert(digits(0) == 1);
342     assert(digits(1) == 1);
343     testDigits(10, 2);
344     testDigits(1_000, 4);
345     testDigits(1_000_000, 7);
346     testDigits(1_000_000_000, 10);
347 }
348 
349 string appendFN( string path, string name )
350 {
351     if (!path.length) return name;
352 
353     version (Windows)
354         const char sep = '\\';
355     else version (Posix)
356         const char sep = '/';
357 
358     auto dest = path;
359 
360     if ( dest.length && dest[$ - 1] != sep )
361         dest ~= sep;
362     dest ~= name;
363     return dest;
364 }
365 
366 
367 string baseName( string name, string ext = null )
368 {
369     string ret;
370     foreach (c; name)
371     {
372         switch (c)
373         {
374         case ':':
375         case '\\':
376         case '/':
377             ret ~= '-';
378             break;
379         default:
380             ret ~= c;
381         }
382     }
383     return ext.length ? chomp(ret,  ext) : ret;
384 }
385 
386 
387 string getExt( string name )
388 {
389     auto i = name.length;
390 
391     while ( i > 0 )
392     {
393         if ( name[i - 1] == '.' )
394             return name[i .. $];
395         --i;
396         version (Windows)
397         {
398             if ( name[i] == ':' || name[i] == '\\' )
399                 break;
400         }
401         else version (Posix)
402         {
403             if ( name[i] == '/' )
404                 break;
405         }
406     }
407     return null;
408 }
409 
410 
411 string addExt( string name, string ext )
412 {
413     auto  existing = getExt( name );
414 
415     if ( existing.length == 0 )
416     {
417         if ( name.length && name[$ - 1] == '.' )
418             name ~= ext;
419         else
420             name = name ~ "." ~ ext;
421     }
422     else
423     {
424         name = name[0 .. $ - existing.length] ~ ext;
425     }
426     return name;
427 }
428 
429 
430 string chomp( string str, string delim = null )
431 {
432     if ( delim is null )
433     {
434         auto len = str.length;
435 
436         if ( len )
437         {
438             auto c = str[len - 1];
439 
440             if ( c == '\r' )
441                 --len;
442             else if ( c == '\n' && str[--len - 1] == '\r' )
443                 --len;
444         }
445         return str[0 .. len];
446     }
447     else if ( str.length >= delim.length )
448     {
449         if ( str[$ - delim.length .. $] == delim )
450             return str[0 .. $ - delim.length];
451     }
452     return str;
453 }
454 
455 // open/create file for read/write, pointer at beginning
456 FILE* openOrCreateFile(string name)
457 {
458     import core.internal.utf : toUTF16z;
459 
460     version (Windows)
461         immutable fd = _wopen(toUTF16z(name), _O_RDWR | _O_CREAT | _O_BINARY, _S_IREAD | _S_IWRITE);
462     else
463         immutable fd = open((name ~ '\0').ptr, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
464                 S_IROTH | S_IWOTH);
465     version (CRuntime_Microsoft)
466         alias fdopen = _fdopen;
467     version (Posix)
468         import core.sys.posix.stdio;
469     return fdopen(fd, "r+b");
470 }
471 
472 version (Windows) HANDLE handle(int fd)
473 {
474     version (CRuntime_DigitalMars)
475         return _fdToHandle(fd);
476     else
477         return cast(HANDLE)_get_osfhandle(fd);
478 }
479 
480 void lockFile(int fd)
481 {
482     version (CRuntime_Bionic)
483     {
484         import core.sys.bionic.fcntl : LOCK_EX;
485         import core.sys.bionic.unistd : flock;
486         flock(fd, LOCK_EX); // exclusive lock
487     }
488     else version (Posix)
489         lockf(fd, F_LOCK, 0); // exclusive lock
490     else version (Windows)
491     {
492         OVERLAPPED off;
493         // exclusively lock first byte
494         LockFileEx(handle(fd), LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &off);
495     }
496     else
497         static assert(0, "unimplemented");
498 }
499 
500 bool readFile(FILE* file, ref char[] buf)
501 {
502     if (fseek(file, 0, SEEK_END) != 0)
503         assert(0, "fseek failed");
504     immutable len = ftell(file);
505     if (len == -1)
506         assert(0, "ftell failed");
507     else if (len == 0)
508         return false;
509 
510     buf.length = len;
511     fseek(file, 0, SEEK_SET);
512     if (fread(buf.ptr, 1, buf.length, file) != buf.length)
513         assert(0, "fread failed");
514     if (fgetc(file) != EOF)
515         assert(0, "EOF not reached");
516     return true;
517 }
518 
519 version (Windows) extern (C) nothrow @nogc FILE* _wfopen(scope const wchar* filename, scope const wchar* mode);
520 version (Windows) extern (C) int chsize(int fd, c_long size);
521 
522 
523 bool readFile(string name, ref char[] buf)
524 {
525     import core.internal.utf : toUTF16z;
526 
527     version (Windows)
528         auto file = _wfopen(toUTF16z(name), "rb"w.ptr);
529     else
530         auto file = fopen((name ~ '\0').ptr, "rb".ptr);
531     if (file is null) return false;
532     scope(exit) fclose(file);
533     return readFile(file, buf);
534 }
535 
536 void splitLines( char[] buf, ref char[][] lines )
537 {
538     size_t  beg = 0,
539             pos = 0;
540 
541     lines.length = 0;
542     for ( ; pos < buf.length; ++pos )
543     {
544         char c = buf[pos];
545 
546         switch ( buf[pos] )
547         {
548         case '\r':
549         case '\n':
550             lines ~= buf[beg .. pos];
551             beg = pos + 1;
552             if ( buf[pos] == '\r' && pos < buf.length - 1 && buf[pos + 1] == '\n' )
553             {
554                 ++pos; ++beg;
555             }
556             continue;
557         default:
558             continue;
559         }
560     }
561     if ( beg != pos )
562     {
563         lines ~= buf[beg .. pos];
564     }
565 }
566 
567 
568 char[] expandTabs( char[] str, int tabsize = 8 )
569 {
570     const dchar LS = '\u2028'; // UTF line separator
571     const dchar PS = '\u2029'; // UTF paragraph separator
572 
573     bool changes = false;
574     char[] result = str;
575     int column;
576     int nspaces;
577 
578     foreach ( size_t i, dchar c; str )
579     {
580         switch ( c )
581         {
582             case '\t':
583                 nspaces = tabsize - (column % tabsize);
584                 if ( !changes )
585                 {
586                     changes = true;
587                     result = null;
588                     result.length = str.length + nspaces - 1;
589                     result.length = i + nspaces;
590                     result[0 .. i] = str[0 .. i];
591                     result[i .. i + nspaces] = ' ';
592                 }
593                 else
594                 {   auto j = result.length;
595                     result.length = j + nspaces;
596                     result[j .. j + nspaces] = ' ';
597                 }
598                 column += nspaces;
599                 break;
600 
601             case '\r':
602             case '\n':
603             case PS:
604             case LS:
605                 column = 0;
606                 goto L1;
607 
608             default:
609                 column++;
610             L1:
611                 if (changes)
612                 {
613                     if (c <= 0x7F)
614                         result ~= cast(char)c;
615                     else
616                     {
617                         dchar[1] ca = c;
618                         foreach (char ch; ca[])
619                             result ~= ch;
620                     }
621                 }
622                 break;
623         }
624     }
625     return result;
626 }