4
4
import java .io .File ;
5
5
import java .io .IOException ;
6
6
import java .util .Arrays ;
7
+ import java .util .Collections ;
8
+ import java .util .Comparator ;
7
9
import java .util .Enumeration ;
8
10
import java .util .HashMap ;
9
11
import java .util .LinkedHashMap ;
12
+ import java .util .LinkedList ;
13
+ import java .util .List ;
10
14
import java .util .Map ;
11
15
import java .util .Objects ;
16
+ import java .util .regex .Matcher ;
12
17
import java .util .regex .Pattern ;
13
18
import java .util .zip .ZipEntry ;
14
19
import java .util .zip .ZipFile ;
29
34
30
35
import com .semmle .util .concurrent .LockDirectory ;
31
36
import com .semmle .util .concurrent .LockDirectory .LockingMode ;
37
+ import com .semmle .util .data .Pair ;
32
38
import com .semmle .util .exception .CatastrophicError ;
33
39
import com .semmle .util .exception .NestedError ;
34
40
import com .semmle .util .exception .ResourceError ;
43
49
import com .semmle .util .trap .pathtransformers .PathTransformer ;
44
50
45
51
public class OdasaOutput {
52
+ // By default we use lockless TRAP writing, but this can be set
53
+ // if we want to use the old TRAP locking for any reason.
54
+ private final boolean use_trap_locking = Env .systemEnv ().getBoolean ("CODEQL_EXTRACTOR_JAVA_TRAP_LOCKING" , false );
46
55
47
56
// either these are set ...
48
57
private final File trapFolder ;
@@ -261,21 +270,39 @@ private void deleteTrapFileAndDependencies(IrDeclaration sym, String signature)
261
270
* For functions for example, this means its parameter signature.
262
271
*/
263
272
private TrapFileManager getMembersWriterForDecl (File trap , IrDeclaration sym , String signature ) {
264
- TrapClassVersion currVersion = TrapClassVersion .fromSymbol (sym , log );
265
- String shortName = sym instanceof IrDeclarationWithName ? ((IrDeclarationWithName )sym ).getName ().asString () : "(name unknown)" ;
266
- if (trap .exists ()) {
267
- // Only re-write an existing trap file if we encountered a newer version of the same class.
268
- TrapClassVersion trapVersion = readVersionInfo (trap );
269
- if (!currVersion .isValid ()) {
270
- log .warn ("Not rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap );
271
- } else if (currVersion .newerThan (trapVersion )) {
272
- log .trace ("Rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap );
273
- deleteTrapFileAndDependencies (sym , signature );
273
+ if (use_trap_locking ) {
274
+ TrapClassVersion currVersion = TrapClassVersion .fromSymbol (sym , log );
275
+ String shortName = sym instanceof IrDeclarationWithName ? ((IrDeclarationWithName )sym ).getName ().asString () : "(name unknown)" ;
276
+ if (trap .exists ()) {
277
+ // Only re-write an existing trap file if we encountered a newer version of the same class.
278
+ TrapClassVersion trapVersion = readVersionInfo (trap );
279
+ if (!currVersion .isValid ()) {
280
+ log .warn ("Not rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap );
281
+ } else if (currVersion .newerThan (trapVersion )) {
282
+ log .trace ("Rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap );
283
+ deleteTrapFileAndDependencies (sym , signature );
284
+ } else {
285
+ return null ;
286
+ }
274
287
} else {
275
- return null ;
288
+ log . trace ( "Writing trap file for: " + shortName + " " + currVersion + " " + trap ) ;
276
289
}
277
290
} else {
278
- log .trace ("Writing trap file for: " + shortName + " " + currVersion + " " + trap );
291
+ // If the TRAP file already exists then we
292
+ // don't need to write it.
293
+ if (trap .exists ()) {
294
+ log .warn ("Not rewriting trap file for " + trap .toString () + " as it exists" );
295
+ return null ;
296
+ }
297
+ // If the TRAP file was written in the past, and
298
+ // then renamed to its trap-old name, then we
299
+ // don't need to rewrite it only to rename it
300
+ // again.
301
+ File trapOld = new File (trap .getParentFile (), trap .getName ().replace (".trap.gz" , ".trap-old.gz" ));
302
+ if (trapOld .exists ()) {
303
+ log .warn ("Not rewriting trap file for " + trap .toString () + " as the trap-old exists" );
304
+ return null ;
305
+ }
279
306
}
280
307
return trapWriter (trap , sym , signature );
281
308
}
@@ -328,19 +355,24 @@ public void close() {
328
355
}
329
356
330
357
writeTrapDependencies (trapDependenciesForClass );
331
- // Record major/minor version information for extracted class files.
332
- // This is subsequently used to determine whether to re-extract (a newer version of) the same class.
333
- File metadataFile = new File (trapFile .getAbsolutePath ().replace (".trap.gz" , ".metadata" ));
334
- try {
335
- Map <String , String > versionMap = new LinkedHashMap <>();
336
- TrapClassVersion tcv = TrapClassVersion .fromSymbol (sym , log );
337
- versionMap .put (MAJOR_VERSION , String .valueOf (tcv .getMajorVersion ()));
338
- versionMap .put (MINOR_VERSION , String .valueOf (tcv .getMinorVersion ()));
339
- versionMap .put (LAST_MODIFIED , String .valueOf (tcv .getLastModified ()));
340
- versionMap .put (EXTRACTOR_NAME , tcv .getExtractorName ());
341
- FileUtil .writePropertiesCSV (metadataFile , versionMap );
342
- } catch (IOException e ) {
343
- log .warn ("Could not save trap metadata file: " + metadataFile .getAbsolutePath (), e );
358
+
359
+ // If we are using TRAP locking then we
360
+ // need to write a metadata file.
361
+ if (use_trap_locking ) {
362
+ // Record major/minor version information for extracted class files.
363
+ // This is subsequently used to determine whether to re-extract (a newer version of) the same class.
364
+ File metadataFile = new File (trapFile .getAbsolutePath ().replace (".trap.gz" , ".metadata" ));
365
+ try {
366
+ Map <String , String > versionMap = new LinkedHashMap <>();
367
+ TrapClassVersion tcv = TrapClassVersion .fromSymbol (sym , log );
368
+ versionMap .put (MAJOR_VERSION , String .valueOf (tcv .getMajorVersion ()));
369
+ versionMap .put (MINOR_VERSION , String .valueOf (tcv .getMinorVersion ()));
370
+ versionMap .put (LAST_MODIFIED , String .valueOf (tcv .getLastModified ()));
371
+ versionMap .put (EXTRACTOR_NAME , tcv .getExtractorName ());
372
+ FileUtil .writePropertiesCSV (metadataFile , versionMap );
373
+ } catch (IOException e ) {
374
+ log .warn ("Could not save trap metadata file: " + metadataFile .getAbsolutePath (), e );
375
+ }
344
376
}
345
377
}
346
378
private void writeTrapDependencies (TrapDependencies trapDependencies ) {
@@ -414,6 +446,10 @@ public TrapLocker getTrapLockerForDecl(IrDeclaration sym, String signature) {
414
446
public class TrapLocker implements AutoCloseable {
415
447
private final IrDeclaration sym ;
416
448
private final File trapFile ;
449
+ // trapFileBase is used when doing lockless TRAP file writing.
450
+ // It is trapFile without the #metadata.trap.gz suffix.
451
+ private File trapFileBase = null ;
452
+ private TrapClassVersion trapFileVersion = null ;
417
453
private final String signature ;
418
454
private TrapLocker (IrDeclaration decl , String signature ) {
419
455
this .sym = decl ;
@@ -422,7 +458,16 @@ private TrapLocker(IrDeclaration decl, String signature) {
422
458
log .error ("Null symbol passed for Kotlin TRAP locker" );
423
459
trapFile = null ;
424
460
} else {
425
- trapFile = getTrapFileForDecl (sym , signature );
461
+ File normalTrapFile = getTrapFileForDecl (sym , signature );
462
+ if (use_trap_locking ) {
463
+ trapFile = normalTrapFile ;
464
+ } else {
465
+ // We encode the metadata into the filename, so that the
466
+ // TRAP filenames for different metadatas don't overlap.
467
+ trapFileVersion = TrapClassVersion .fromSymbol (sym , log );
468
+ trapFileBase = new File (normalTrapFile .getParentFile (), normalTrapFile .getName ().replace (".trap.gz" , "" ));
469
+ trapFile = new File (trapFileBase .getPath () + '#' + trapFileVersion .toString () + ".trap.gz" );
470
+ }
426
471
}
427
472
}
428
473
private TrapLocker (File jarFile ) {
@@ -437,20 +482,80 @@ private TrapLocker(String moduleName) {
437
482
}
438
483
public TrapFileManager getTrapFileManager () {
439
484
if (trapFile !=null ) {
440
- lockTrapFile (trapFile );
485
+ if (use_trap_locking ) {
486
+ lockTrapFile (trapFile );
487
+ }
441
488
return getMembersWriterForDecl (trapFile , sym , signature );
442
489
} else {
443
490
return null ;
444
491
}
445
492
}
493
+
494
+ private final Pattern selectClassVersionComponents = Pattern .compile ("(.*)#(-?[0-9]+)\\ .(-?[0-9]+)-(-?[0-9]+)-(.*)\\ .trap\\ .gz" );
495
+
446
496
@ Override
447
497
public void close () {
448
498
if (trapFile !=null ) {
449
499
try {
450
- unlockTrapFile (trapFile );
500
+ if (use_trap_locking ) {
501
+ unlockTrapFile (trapFile );
502
+ }
451
503
} catch (NestedError e ) {
452
504
log .warn ("Error unlocking trap file " + trapFile .getAbsolutePath (), e );
453
505
}
506
+
507
+ // If we are writing TRAP file locklessly, then now that we
508
+ // have finished writing our TRAP file, we want to rename
509
+ // and TRAP file that matches our trapFileBase but doesn't
510
+ // have the latest metadata.
511
+ // Renaming it to trap-old means that it won't be imported,
512
+ // but we can still use its presence to avoid future
513
+ // invocations rewriting it, and it means that the information
514
+ // is in the TRAP directory if we need it for debugging.
515
+ if (!use_trap_locking && sym != null ) {
516
+ File trapFileDir = trapFileBase .getParentFile ();
517
+ String trapFileBaseName = trapFileBase .getName ();
518
+
519
+ List <Pair <File , TrapClassVersion >> pairs = new LinkedList <Pair <File , TrapClassVersion >>();
520
+ for (File f : FileUtil .list (trapFileDir )) {
521
+ String name = f .getName ();
522
+ Matcher m = selectClassVersionComponents .matcher (name );
523
+ if (m .matches () && m .group (1 ).equals (trapFileBaseName )) {
524
+ TrapClassVersion v = new TrapClassVersion (Integer .valueOf (m .group (2 )), Integer .valueOf (m .group (3 )), Long .valueOf (m .group (4 )), m .group (5 ));
525
+ pairs .add (new Pair <File , TrapClassVersion >(f , v ));
526
+ }
527
+ }
528
+ if (pairs .isEmpty ()) {
529
+ log .error ("Wrote TRAP file, but no TRAP files exist for " + trapFile .getAbsolutePath ());
530
+ } else {
531
+ Comparator <Pair <File , TrapClassVersion >> comparator = new Comparator <Pair <File , TrapClassVersion >>() {
532
+ @ Override
533
+ public int compare (Pair <File , TrapClassVersion > p1 , Pair <File , TrapClassVersion > p2 ) {
534
+ TrapClassVersion v1 = p1 .snd ();
535
+ TrapClassVersion v2 = p2 .snd ();
536
+ if (v1 .equals (v2 )) {
537
+ return 0 ;
538
+ } else if (v1 .newerThan (v2 )) {
539
+ return 1 ;
540
+ } else {
541
+ return -1 ;
542
+ }
543
+ }
544
+ };
545
+ TrapClassVersion latestVersion = Collections .max (pairs , comparator ).snd ();
546
+
547
+ for (Pair <File , TrapClassVersion > p : pairs ) {
548
+ if (!latestVersion .equals (p .snd ())) {
549
+ File f = p .fst ();
550
+ File fOld = new File (f .getParentFile (), f .getName ().replace (".trap.gz" , ".trap-old.gz" ));
551
+ // We aren't interested in whether or not this succeeds;
552
+ // it may fail because a concurrent extractor has already
553
+ // renamed it.
554
+ f .renameTo (fOld );
555
+ }
556
+ }
557
+ }
558
+ }
454
559
}
455
560
}
456
561
@@ -505,6 +610,17 @@ private TrapClassVersion(int majorVersion, int minorVersion, long lastModified,
505
610
this .lastModified = lastModified ;
506
611
this .extractorName = extractorName ;
507
612
}
613
+
614
+ @ Override
615
+ public boolean equals (Object obj ) {
616
+ if (obj instanceof TrapClassVersion ) {
617
+ TrapClassVersion other = (TrapClassVersion )obj ;
618
+ return majorVersion == other .majorVersion && minorVersion == other .minorVersion && lastModified == other .lastModified && extractorName .equals (other .extractorName );
619
+ } else {
620
+ return false ;
621
+ }
622
+ }
623
+
508
624
private boolean newerThan (TrapClassVersion tcv ) {
509
625
// Classes being compiled from source have major version 0 but should take precedence
510
626
// over any classes with the same qualified name loaded from the classpath
0 commit comments