@@ -5,11 +5,20 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
5
5
import org.jetbrains.kotlin.ir.declarations.*
6
6
import org.jetbrains.kotlin.ir.util.*
7
7
import org.jetbrains.kotlin.ir.IrElement
8
+ import java.io.BufferedReader
9
+ import java.io.BufferedWriter
10
+ import java.io.BufferedInputStream
11
+ import java.io.BufferedOutputStream
8
12
import java.io.File
13
+ import java.io.FileInputStream
9
14
import java.io.FileOutputStream
15
+ import java.io.InputStreamReader
16
+ import java.io.OutputStreamWriter
10
17
import java.lang.management.*
11
18
import java.nio.file.Files
12
19
import java.nio.file.Paths
20
+ import java.util.zip.GZIPInputStream
21
+ import java.util.zip.GZIPOutputStream
13
22
import com.semmle.util.files.FileUtil
14
23
import kotlin.system.exitProcess
15
24
@@ -89,8 +98,29 @@ class KotlinExtractorExtension(
89
98
val startTimeMs = System .currentTimeMillis()
90
99
// This default should be kept in sync with com.semmle.extractor.java.interceptors.KotlinInterceptor.initializeExtractionContext
91
100
val trapDir = File (System .getenv(" CODEQL_EXTRACTOR_JAVA_TRAP_DIR" ).takeUnless { it.isNullOrEmpty() } ? : " kotlin-extractor/trap" )
101
+ val compression_env_var = " CODEQL_EXTRACTOR_JAVA_OPTION_TRAP_COMPRESSION"
102
+ val compression_option = System .getenv(compression_env_var)
103
+ val defaultCompression = Compression .GZIP
104
+ val (compression, compressionWarning) =
105
+ if (compression_option == null ) {
106
+ Pair (defaultCompression, null )
107
+ } else {
108
+ try {
109
+ @OptIn(kotlin.ExperimentalStdlibApi ::class ) // Annotation required by kotlin versions < 1.5
110
+ val requested_compression = Compression .valueOf(compression_option.uppercase())
111
+ if (requested_compression == Compression .BROTLI ) {
112
+ Pair (Compression .GZIP , " Kotlin extractor doesn't support Brotli compression. Using GZip instead." )
113
+ } else {
114
+ Pair (requested_compression, null )
115
+ }
116
+ } catch (e: IllegalArgumentException ) {
117
+ Pair (defaultCompression,
118
+ " Unsupported compression type (\$ $compression_env_var ) \" $compression_option \" . Supported values are ${Compression .values().joinToString()} " )
119
+ }
120
+ }
92
121
// The invocation TRAP file will already have been started
93
- // before the plugin is run, so we open it in append mode.
122
+ // before the plugin is run, so we always use no compression
123
+ // and we open it in append mode.
94
124
FileOutputStream (File (invocationTrapFile), true ).bufferedWriter().use { invocationTrapFileBW ->
95
125
val invocationExtractionProblems = ExtractionProblems ()
96
126
val lm = TrapLabelManager ()
@@ -113,6 +143,10 @@ class KotlinExtractorExtension(
113
143
if (System .getenv(" CODEQL_EXTRACTOR_JAVA_KOTLIN_DUMP" ) == " true" ) {
114
144
logger.info(" moduleFragment:\n " + moduleFragment.dump())
115
145
}
146
+ if (compressionWarning != null ) {
147
+ logger.warn(compressionWarning)
148
+ }
149
+
116
150
val primitiveTypeMapping = PrimitiveTypeMapping (logger, pluginContext)
117
151
// FIXME: FileUtil expects a static global logger
118
152
// which should be provided by SLF4J's factory facility. For now we set it here.
@@ -125,7 +159,7 @@ class KotlinExtractorExtension(
125
159
val fileTrapWriter = tw.makeSourceFileTrapWriter(file, true )
126
160
loggerBase.setFileNumber(index)
127
161
fileTrapWriter.writeCompilation_compiling_files(compilation, index, fileTrapWriter.fileId)
128
- doFile(fileExtractionProblems, invocationTrapFile, fileTrapWriter, checkTrapIdentical, loggerBase, trapDir, srcDir, file, primitiveTypeMapping, pluginContext, globalExtensionState)
162
+ doFile(compression, fileExtractionProblems, invocationTrapFile, fileTrapWriter, checkTrapIdentical, loggerBase, trapDir, srcDir, file, primitiveTypeMapping, pluginContext, globalExtensionState)
129
163
fileTrapWriter.writeCompilation_compiling_files_completed(compilation, index, fileExtractionProblems.extractionResult())
130
164
}
131
165
loggerBase.printLimitedDiagnosticCounts(tw)
@@ -218,12 +252,12 @@ This function determines whether 2 TRAP files should be considered to be
218
252
equivalent. It returns `true` iff all of their non-comment lines are
219
253
identical.
220
254
*/
221
- private fun equivalentTrap (f1 : File , f2 : File ): Boolean {
222
- f1.bufferedReader(). use { bw1 ->
223
- f2.bufferedReader(). use { bw2 ->
255
+ private fun equivalentTrap (r1 : BufferedReader , r2 : BufferedReader ): Boolean {
256
+ r1. use { br1 ->
257
+ r2. use { br2 ->
224
258
while (true ) {
225
- val l1 = bw1 .readLine()
226
- val l2 = bw2 .readLine()
259
+ val l1 = br1 .readLine()
260
+ val l2 = br2 .readLine()
227
261
if (l1 == null && l2 == null ) {
228
262
return true
229
263
} else if (l1 == null || l2 == null ) {
@@ -239,6 +273,7 @@ private fun equivalentTrap(f1: File, f2: File): Boolean {
239
273
}
240
274
241
275
private fun doFile (
276
+ compression : Compression ,
242
277
fileExtractionProblems : FileExtractionProblems ,
243
278
invocationTrapFile : String ,
244
279
fileTrapWriter : FileTrapWriter ,
@@ -270,15 +305,14 @@ private fun doFile(
270
305
}
271
306
srcTmpFile.renameTo(dbSrcFilePath.toFile())
272
307
273
- val trapFile = File (" $dbTrapDir /$srcFilePath .trap" )
274
- val trapFileDir = trapFile.parentFile
275
- trapFileDir.mkdirs()
308
+ val trapFileName = " $dbTrapDir /$srcFilePath .trap"
309
+ val trapFileWriter = getTrapFileWriter(compression, logger, trapFileName)
276
310
277
- if (checkTrapIdentical || ! trapFile .exists()) {
278
- val trapTmpFile = File .createTempFile( " $srcFilePath . " , " .trap.tmp " , trapFileDir )
311
+ if (checkTrapIdentical || ! trapFileWriter .exists()) {
312
+ trapFileWriter.makeParentDirectory( )
279
313
280
314
try {
281
- trapTmpFile.bufferedWriter ().use { trapFileBW ->
315
+ trapFileWriter.getTempWriter ().use { trapFileBW ->
282
316
// We want our comments to be the first thing in the file,
283
317
// so start off with a mere TrapWriter
284
318
val tw = TrapWriter (loggerBase, TrapLabelManager (), trapFileBW, fileTrapWriter)
@@ -294,31 +328,114 @@ private fun doFile(
294
328
externalDeclExtractor.extractExternalClasses()
295
329
}
296
330
297
- if (checkTrapIdentical && trapFile.exists()) {
298
- if (equivalentTrap(trapTmpFile, trapFile)) {
299
- if (! trapTmpFile.delete()) {
300
- logger.warn(" Failed to delete $trapTmpFile " )
301
- }
331
+ if (checkTrapIdentical && trapFileWriter.exists()) {
332
+ if (equivalentTrap(trapFileWriter.getTempReader(), trapFileWriter.getRealReader())) {
333
+ trapFileWriter.deleteTemp()
302
334
} else {
303
- val trapDifferentFile = File .createTempFile(" $srcFilePath ." , " .trap.different" , dbTrapDir)
304
- if (trapTmpFile.renameTo(trapDifferentFile)) {
305
- logger.warn(" TRAP difference: $trapFile vs $trapDifferentFile " )
306
- } else {
307
- logger.warn(" Failed to rename $trapTmpFile to $trapFile " )
308
- }
335
+ trapFileWriter.renameTempToDifferent()
309
336
}
310
337
} else {
311
- if (! trapTmpFile.renameTo(trapFile)) {
312
- logger.warn(" Failed to rename $trapTmpFile to $trapFile " )
313
- }
338
+ trapFileWriter.renameTempToReal()
314
339
}
315
340
// We catch Throwable rather than Exception, as we want to
316
341
// continue trying to extract everything else even if we get a
317
342
// stack overflow or an assertion failure in one file.
318
343
} catch (e: Throwable ) {
319
- logger.error(" Failed to extract '$srcFilePath '. Partial TRAP file location is $trapTmpFile " , e)
344
+ logger.error(" Failed to extract '$srcFilePath '. " + trapFileWriter.debugInfo() , e)
320
345
context.clear()
321
346
fileExtractionProblems.setNonRecoverableProblem()
322
347
}
323
348
}
324
349
}
350
+
351
+ enum class Compression { NONE , GZIP , BROTLI }
352
+
353
+ private fun getTrapFileWriter (compression : Compression , logger : FileLogger , trapFileName : String ): TrapFileWriter {
354
+ return when (compression) {
355
+ Compression .NONE -> NonCompressedTrapFileWriter (logger, trapFileName)
356
+ Compression .GZIP -> GZipCompressedTrapFileWriter (logger, trapFileName)
357
+ Compression .BROTLI -> throw Exception (" Brotli compression is not supported by the Kotlin extractor" )
358
+ }
359
+ }
360
+
361
+ private abstract class TrapFileWriter (val logger : FileLogger , trapName : String , val extension : String ) {
362
+ private val realFile = File (trapName + extension)
363
+ private val parentDir = realFile.parentFile
364
+ lateinit private var tempFile: File
365
+
366
+ fun debugInfo (): String {
367
+ if (this ::tempFile.isInitialized) {
368
+ return " Partial TRAP file location is $tempFile "
369
+ } else {
370
+ return " Temporary file not yet created."
371
+ }
372
+ }
373
+
374
+ fun makeParentDirectory () {
375
+ parentDir.mkdirs()
376
+ }
377
+
378
+ fun exists (): Boolean {
379
+ return realFile.exists()
380
+ }
381
+
382
+ abstract protected fun getReader (file : File ): BufferedReader
383
+ abstract protected fun getWriter (file : File ): BufferedWriter
384
+
385
+ fun getRealReader (): BufferedReader {
386
+ return getReader(realFile)
387
+ }
388
+
389
+ fun getTempReader (): BufferedReader {
390
+ return getReader(tempFile)
391
+ }
392
+
393
+ fun getTempWriter (): BufferedWriter {
394
+ if (this ::tempFile.isInitialized) {
395
+ logger.error(" Temp writer reinitiailised for $realFile " )
396
+ }
397
+ tempFile = File .createTempFile(realFile.getName() + " ." , " .trap.tmp" + extension, parentDir)
398
+ return getWriter(tempFile)
399
+ }
400
+
401
+ fun deleteTemp () {
402
+ if (! tempFile.delete()) {
403
+ logger.warn(" Failed to delete $tempFile " )
404
+ }
405
+ }
406
+
407
+ fun renameTempToDifferent () {
408
+ val trapDifferentFile = File .createTempFile(realFile.getName() + " ." , " .trap.different" + extension, parentDir)
409
+ if (tempFile.renameTo(trapDifferentFile)) {
410
+ logger.warn(" TRAP difference: $realFile vs $trapDifferentFile " )
411
+ } else {
412
+ logger.warn(" Failed to rename $tempFile to $realFile " )
413
+ }
414
+ }
415
+
416
+ fun renameTempToReal () {
417
+ if (! tempFile.renameTo(realFile)) {
418
+ logger.warn(" Failed to rename $tempFile to $realFile " )
419
+ }
420
+ }
421
+ }
422
+
423
+ private class NonCompressedTrapFileWriter (logger : FileLogger , trapName : String ): TrapFileWriter(logger, trapName, " " ) {
424
+ override protected fun getReader (file : File ): BufferedReader {
425
+ return file.bufferedReader()
426
+ }
427
+
428
+ override protected fun getWriter (file : File ): BufferedWriter {
429
+ return file.bufferedWriter()
430
+ }
431
+ }
432
+
433
+ private class GZipCompressedTrapFileWriter (logger : FileLogger , trapName : String ): TrapFileWriter(logger, trapName, " .gz" ) {
434
+ override protected fun getReader (file : File ): BufferedReader {
435
+ return BufferedReader (InputStreamReader (GZIPInputStream (BufferedInputStream (FileInputStream (file)))))
436
+ }
437
+
438
+ override protected fun getWriter (file : File ): BufferedWriter {
439
+ return BufferedWriter (OutputStreamWriter (GZIPOutputStream (BufferedOutputStream (FileOutputStream (file)))))
440
+ }
441
+ }
0 commit comments