@@ -5,11 +5,20 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
55import org.jetbrains.kotlin.ir.declarations.*
66import org.jetbrains.kotlin.ir.util.*
77import 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
812import java.io.File
13+ import java.io.FileInputStream
914import java.io.FileOutputStream
15+ import java.io.InputStreamReader
16+ import java.io.OutputStreamWriter
1017import java.lang.management.*
1118import java.nio.file.Files
1219import java.nio.file.Paths
20+ import java.util.zip.GZIPInputStream
21+ import java.util.zip.GZIPOutputStream
1322import com.semmle.util.files.FileUtil
1423import kotlin.system.exitProcess
1524
@@ -89,8 +98,29 @@ class KotlinExtractorExtension(
8998 val startTimeMs = System .currentTimeMillis()
9099 // This default should be kept in sync with com.semmle.extractor.java.interceptors.KotlinInterceptor.initializeExtractionContext
91100 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+ }
92121 // 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.
94124 FileOutputStream (File (invocationTrapFile), true ).bufferedWriter().use { invocationTrapFileBW ->
95125 val invocationExtractionProblems = ExtractionProblems ()
96126 val lm = TrapLabelManager ()
@@ -113,6 +143,10 @@ class KotlinExtractorExtension(
113143 if (System .getenv(" CODEQL_EXTRACTOR_JAVA_KOTLIN_DUMP" ) == " true" ) {
114144 logger.info(" moduleFragment:\n " + moduleFragment.dump())
115145 }
146+ if (compressionWarning != null ) {
147+ logger.warn(compressionWarning)
148+ }
149+
116150 val primitiveTypeMapping = PrimitiveTypeMapping (logger, pluginContext)
117151 // FIXME: FileUtil expects a static global logger
118152 // which should be provided by SLF4J's factory facility. For now we set it here.
@@ -125,7 +159,7 @@ class KotlinExtractorExtension(
125159 val fileTrapWriter = tw.makeSourceFileTrapWriter(file, true )
126160 loggerBase.setFileNumber(index)
127161 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)
129163 fileTrapWriter.writeCompilation_compiling_files_completed(compilation, index, fileExtractionProblems.extractionResult())
130164 }
131165 loggerBase.printLimitedDiagnosticCounts(tw)
@@ -218,12 +252,12 @@ This function determines whether 2 TRAP files should be considered to be
218252equivalent. It returns `true` iff all of their non-comment lines are
219253identical.
220254*/
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 ->
224258 while (true ) {
225- val l1 = bw1 .readLine()
226- val l2 = bw2 .readLine()
259+ val l1 = br1 .readLine()
260+ val l2 = br2 .readLine()
227261 if (l1 == null && l2 == null ) {
228262 return true
229263 } else if (l1 == null || l2 == null ) {
@@ -239,6 +273,7 @@ private fun equivalentTrap(f1: File, f2: File): Boolean {
239273}
240274
241275private fun doFile (
276+ compression : Compression ,
242277 fileExtractionProblems : FileExtractionProblems ,
243278 invocationTrapFile : String ,
244279 fileTrapWriter : FileTrapWriter ,
@@ -270,15 +305,14 @@ private fun doFile(
270305 }
271306 srcTmpFile.renameTo(dbSrcFilePath.toFile())
272307
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)
276310
277- if (checkTrapIdentical || ! trapFile .exists()) {
278- val trapTmpFile = File .createTempFile( " $srcFilePath . " , " .trap.tmp " , trapFileDir )
311+ if (checkTrapIdentical || ! trapFileWriter .exists()) {
312+ trapFileWriter.makeParentDirectory( )
279313
280314 try {
281- trapTmpFile.bufferedWriter ().use { trapFileBW ->
315+ trapFileWriter.getTempWriter ().use { trapFileBW ->
282316 // We want our comments to be the first thing in the file,
283317 // so start off with a mere TrapWriter
284318 val tw = TrapWriter (loggerBase, TrapLabelManager (), trapFileBW, fileTrapWriter)
@@ -294,31 +328,114 @@ private fun doFile(
294328 externalDeclExtractor.extractExternalClasses()
295329 }
296330
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()
302334 } 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()
309336 }
310337 } else {
311- if (! trapTmpFile.renameTo(trapFile)) {
312- logger.warn(" Failed to rename $trapTmpFile to $trapFile " )
313- }
338+ trapFileWriter.renameTempToReal()
314339 }
315340 // We catch Throwable rather than Exception, as we want to
316341 // continue trying to extract everything else even if we get a
317342 // stack overflow or an assertion failure in one file.
318343 } 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)
320345 context.clear()
321346 fileExtractionProblems.setNonRecoverableProblem()
322347 }
323348 }
324349}
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