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 }