1 /**
2  * Code coverage analyzer.
3  *
4  * Bugs:
5  *      $(UL
6  *          $(LI the execution counters are 32 bits in size, and can overflow)
7  *          $(LI inline asm statements are not counted)
8  *      )
9  *
10  * Copyright: Copyright (C) 2005-2006 Digital Mars, www.digitalmars.com.  All rights reserved.
11  * License:   BSD style: $(LICENSE)
12  * Authors:   Walter Bright, Sean Kelly
13  */
14 module rt.compiler.dmd.rt.cover;
15 // ugly, uses many different file interfaces, could it be reduced to just the stdc ones?
16 
17 private
18 {
19     version( Win32 ) {
20         import tango.sys.win32.UserGdi : HANDLE,THANDLE,LPCWSTR,DWORD,
21                                          LPSECURITY_ATTRIBUTES,WINBOOL, GENERIC_READ,
22                                          FILE_SHARE_READ,OPEN_EXISTING,
23                                          FILE_ATTRIBUTE_NORMAL,FILE_FLAG_SEQUENTIAL_SCAN,
24                                          INVALID_HANDLE_VALUE,POVERLAPPED,CreateFileW,
25                                          CloseHandle,ReadFile;
26     } else version( Posix ) {
27         import tango.stdc.posix.fcntl : open, O_RDONLY;
28         import tango.stdc.posix.unistd : close, read;
29     }
30     import tango.core.BitManip;
31     import tango.stdc.stdio : fopen,fclose,fprintf,FILE;
32     import rt.compiler.util.utf;
33 
34     struct BitArray
35     {
36         size_t  len;
37         size_t* ptr;
38 
39         bool opIndex( size_t i )
40         in
41         {
42             assert( i < len );
43         }
44         body
45         {
46             return cast(bool) bt( ptr, i );
47         }
48     }
49 
50     struct Cover
51     {
52         char[]      filename;
53         BitArray    valid;
54         uint[]      data;
55     }
56 
57     Cover[] gdata;
58     char[]  srcpath;
59     char[]  dstpath;
60     bool    merge;
61 }
62 
63 
64 /**
65  * Set path to where source files are located.
66  *
67  * Params:
68  *  pathname = The new path name.
69  */
70 extern (C) void dmd_coverSourcePath( char[] pathname )
71 {
72     srcpath = pathname;
73 }
74 
75 
76 /**
77  * Set path to where listing files are to be written.
78  *
79  * Params:
80  *  pathname = The new path name.
81  */
82 extern (C) void dmd_coverDestPath( char[] pathname )
83 {
84     dstpath = pathname;
85 }
86 
87 
88 /**
89  * Set merge mode.
90  *
91  * Params:
92  *      flag = true means new data is summed with existing data in the listing
93  *         file; false means a new listing file is always created.
94  */
95 extern (C) void dmd_coverSetMerge( bool flag )
96 {
97     merge = flag;
98 }
99 
100 
101 /**
102  * The coverage callback.
103  *
104  * Params:
105  *  filename = The name of the coverage file.
106  *  valid    = ???
107  *  data     = ???
108  */
109 extern (C) void _d_cover_register( char[] filename, BitArray valid, uint[] data )
110 {
111     Cover c;
112 
113     c.filename  = filename;
114     c.valid     = valid;
115     c.data      = data;
116     gdata      ~= c;
117 }
118 
119 
120 static ~this()
121 {
122     const NUMLINES = 16384 - 1;
123     const NUMCHARS = 16384 * 16 - 1;
124 
125     char[]      srcbuf      = new char[NUMCHARS];
126     char[][]    srclines    = new char[][NUMLINES];
127     char[]      lstbuf      = new char[NUMCHARS];
128     char[][]    lstlines    = new char[][NUMLINES];
129 
130     foreach( Cover c; gdata )
131     {
132         if( !readFile( appendFN( srcpath, c.filename ), srcbuf ) )
133             continue;
134         splitLines( srcbuf, srclines );
135 
136         if( merge )
137         {
138             if( !readFile( addExt( baseName( c.filename ), "lst" ), lstbuf ) )
139                 break;
140             splitLines( lstbuf, lstlines );
141 
142             for( size_t i = 0; i < lstlines.length; ++i )
143             {
144                 if( i >= c.data.length )
145                     break;
146 
147                 int count = 0;
148 
149                 foreach( char c2; lstlines[i] )
150                 {
151                     switch( c2 )
152                     {
153                     case ' ':
154                         continue;
155                     case '0': case '1': case '2': case '3': case '4':
156                     case '5': case '6': case '7': case '8': case '9':
157                         count = count * 10 + c2 - '0';
158                         continue;
159                     default:
160                         break;
161                     }
162                 }
163                 c.data[i] += count;
164             }
165         }
166 
167         FILE* flst = fopen( (addExt( baseName( c.filename ), "lst\0" )).ptr, "wb" );
168 
169         if( !flst )
170             continue; //throw new Exception( "Error opening file for write: " ~ lstfn );
171 
172         uint nno;
173         uint nyes;
174 
175         for( int i = 0; i < c.data.length; i++ )
176         {
177             if( i < srclines.length )
178             {
179                 uint    n    = c.data[i];
180                 char[]  line = srclines[i];
181 
182                 line = expandTabs( line );
183 
184                 if( n == 0 )
185                 {
186                     if( c.valid[i] )
187                     {
188                         nno++;
189                         fprintf( flst, "0000000|%.*s\n", line );
190                     }
191                     else
192                     {
193                         fprintf( flst, "       |%.*s\n", line );
194                     }
195                 }
196                 else
197                 {
198                     nyes++;
199                     fprintf( flst, "%7u|%.*s\n", n, line );
200                 }
201             }
202         }
203         if( nyes + nno ) // no divide by 0 bugs
204         {
205             fprintf( flst, "%.*s is %d%% covered\n", c.filename, ( nyes * 100 ) / ( nyes + nno ) );
206         }
207         fclose( flst );
208     }
209 }
210 
211 
212 char[] appendFN( char[] path, char[] name )
213 {
214     version( Windows )
215         const char sep = '\\';
216     else
217         const char sep = '/';
218 
219     auto dest = path;
220 
221     if( dest && dest[$ - 1] != sep )
222         dest ~= sep;
223     dest ~= name;
224     return dest;
225 }
226 
227 
228 char[] baseName( char[] name, char[] ext = null )
229 {
230     auto i = name.length;
231     auto ret = name.dup;
232     version( Windows )
233     {
234         const DELIMITER = '\\';
235     }
236     else version( Posix )
237     {
238         const DELIMITER = '/';
239     }
240     for( ; i > 0; --i )
241     {
242         if( ret[i - 1] == ':' || ret[i - 1] == '\\'  || ret[i - 1] == '/' )
243             ret[i - 1] = '.';
244     }
245     return (dstpath ? dstpath ~ DELIMITER : "") ~ chomp( ret[i .. $], ext ? ext : "" );
246 }
247 
248 
249 char[] getExt( char[] name )
250 {
251     auto i = name.length;
252 
253     while( i > 0 )
254     {
255         if( name[i - 1] == '.' )
256             return name[i .. $];
257         --i;
258         version( Windows )
259         {
260             if( name[i] == ':' || name[i] == '\\' )
261                 break;
262         }
263         else version( Posix )
264         {
265             if( name[i] == '/' )
266                 break;
267         }
268     }
269     return null;
270 }
271 
272 
273 char[] addExt( char[] name, char[] ext )
274 {
275     auto  existing = getExt( name );
276 
277     if( existing.length == 0 )
278     {
279         if( name.length && name[$ - 1] == '.' )
280             name ~= ext;
281         else
282             name = name ~ "." ~ ext;
283     }
284     else
285     {
286         name = name[0 .. $ - existing.length] ~ ext;
287     }
288     return name;
289 }
290 
291 
292 char[] chomp( char[] str, char[] delim = null )
293 {
294     if( delim is null )
295     {
296         auto len = str.length;
297 
298         if( len )
299         {
300             auto c = str[len - 1];
301 
302             if( c == '\r' )
303                 --len;
304             else if( c == '\n' && str[--len - 1] == '\r' )
305                 --len;
306         }
307         return str[0 .. len];
308     }
309     else if( str.length >= delim.length )
310     {
311         if( str[$ - delim.length .. $] == delim )
312             return str[0 .. $ - delim.length];
313     }
314     return str;
315 }
316 
317 
318 bool readFile( char[] name, ref char[] buf )
319 {
320     version( Win32 )
321     {
322         wchar*  wnamez  = toUTF16z( name );
323         HANDLE  file    = CreateFileW( wnamez,
324                                        GENERIC_READ,
325                                        FILE_SHARE_READ,
326                                        null,
327                                        OPEN_EXISTING,
328                                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
329                                        cast(HANDLE) null );
330 
331         delete wnamez;
332         if( file == INVALID_HANDLE_VALUE )
333             return false;
334         scope( exit ) CloseHandle( file );
335 
336         DWORD   num = 0;
337         DWORD   pos = 0;
338 
339         buf.length = 4096;
340         while( true )
341         {
342             if( !ReadFile( file, &buf[pos], cast(DWORD)( buf.length - pos ), &num, null ) )
343                 return false;
344             if( !num )
345                 break;
346             pos += num;
347             buf.length = pos * 2;
348         }
349         buf.length = pos;
350         return true;
351     }
352     else version( Posix )
353     {
354         char[]  namez = new char[name.length + 1];
355                         namez[0 .. name.length] = name;
356                         namez[$ - 1] = 0;
357         int     file = open( namez.ptr, O_RDONLY );
358 
359         delete namez;
360         if( file == -1 )
361             return false;
362         scope( exit ) close( file );
363 
364         int     num = 0;
365         uint    pos = 0;
366 
367         buf.length = 4096;
368         while( true )
369         {
370             num = read( file, &buf[pos], cast(uint)( buf.length - pos ) );
371             if( num == -1 )
372                 return false;
373             if( !num )
374                 break;
375             pos += num;
376             buf.length = pos * 2;
377         }
378         buf.length = pos;
379         return true;
380     }
381 }
382 
383 
384 void splitLines( char[] buf, ref char[][] lines )
385 {
386     size_t  beg = 0,
387             pos = 0;
388 
389     lines.length = 0;
390     for( ; pos < buf.length; ++pos )
391     {
392         char c = buf[pos];
393 
394         switch( buf[pos] )
395         {
396         case '\r':
397         case '\n':
398             lines ~= buf[beg .. pos];
399             beg = pos + 1;
400             if( buf[pos] == '\r' && pos < buf.length - 1 && buf[pos + 1] == '\n' )
401                 ++pos, ++beg;
402         default:
403             continue;
404         }
405     }
406     if( beg != pos )
407     {
408         lines ~= buf[beg .. pos];
409     }
410 }
411 
412 
413 char[] expandTabs( char[] str, int tabsize = 8 )
414 {
415     const dchar LS = '\u2028'; // UTF line separator
416     const dchar PS = '\u2029'; // UTF paragraph separator
417 
418     bool changes = false;
419     char[] result = str;
420     int column;
421     int nspaces;
422 
423     foreach( size_t i, dchar c; str )
424     {
425         switch( c )
426         {
427             case '\t':
428                 nspaces = tabsize - (column % tabsize);
429                 if( !changes )
430                 {
431                     changes = true;
432                     result = null;
433                     result.length = str.length + nspaces - 1;
434                     result.length = i + nspaces;
435                     result[0 .. i] = str[0 .. i];
436                     result[i .. i + nspaces] = ' ';
437                 }
438                 else
439                 {   int j = result.length;
440                     result.length = j + nspaces;
441                     result[j .. j + nspaces] = ' ';
442                 }
443                 column += nspaces;
444                 break;
445 
446             case '\r':
447             case '\n':
448             case PS:
449             case LS:
450                 column = 0;
451                 goto L1;
452 
453             default:
454                 column++;
455             L1:
456                 if (changes)
457                 {
458                     if (c <= 0x7F)
459                         result ~= c;
460                     else
461                         encode(result, c);
462                 }
463                 break;
464         }
465     }
466     return result;
467 }