All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.jimple2cpg.util.ProgramHandlingUtil.scala Maven / Gradle / Ivy

The newest version!
package io.joern.jimple2cpg.util

import io.joern.x2cpg.SourceFiles
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader.SKIP_CODE
import org.objectweb.asm.{ClassReader, ClassVisitor, Opcodes}
import org.slf4j.LoggerFactory

import java.io.{File, FileInputStream}
import java.nio.file.{Files, Path, Paths, StandardCopyOption}
import java.util.zip.ZipFile
import scala.jdk.CollectionConverters.EnumerationHasAsScala
import scala.util.Using

/** Responsible for handling JAR unpacking and handling the temporary build directory.
  */
object ProgramHandlingUtil {

  private val logger = LoggerFactory.getLogger(ProgramHandlingUtil.getClass)

  /** The temporary directory used to unpack class files to.
    */
  private var TEMP_DIR: Option[Path] = None

  logger.debug(s"Using temporary folder at $TEMP_DIR")

  /** Returns the temporary directory used to unpack and analyze projects in. This allows us to lazily create the
    * unpacking directory.
    * @return
    *   the path pointing to the unpacking directory.
    */
  def getUnpackingDir: Path =
    TEMP_DIR match {
      case None =>
        val p = Files.createTempDirectory("joern-")
        TEMP_DIR = Some(p)
        p
      case Some(dir) => dir
    }

  /** Inspects class files and moves them to the temp directory based on their package path.
    *
    * @param files
    *   the class files to move.
    * @return
    *   the list of class files at their new locations.
    */
  def moveClassFiles(files: List[String]): List[String] = {
    var destPath: Option[String] = None

    sealed class ClassPathVisitor extends ClassVisitor(Opcodes.ASM9) {
      override def visit(
        version: Int,
        access: Int,
        name: String,
        signature: String,
        superName: String,
        interfaces: Array[String]
      ): Unit = {
        destPath = Some(getUnpackingDir.toAbsolutePath.toString + File.separator + name + ".class")
      }
    }

    files.flatMap { f =>
      Using.resource(new FileInputStream(f)) { fis =>
        val cr          = new ClassReader(fis)
        val rootVisitor = new ClassPathVisitor()
        cr.accept(rootVisitor, SKIP_CODE)
      }
      destPath match {
        case Some(destPath) =>
          val dstFile = new File(destPath)
          dstFile.mkdirs()
          Files.copy(Paths.get(f), dstFile.toPath, StandardCopyOption.REPLACE_EXISTING)
          Some(dstFile.getAbsolutePath)
        case None => None
      }
    }
  }

  /** Unzips a ZIP file into a sequence of files. All files unpacked are deleted at the end of CPG construction.
    *
    * @param zf
    *   The ZIP file to extract.
    * @param sourceCodePath
    *   The project root path to unpack to.
    */
  def unzipArchive(zf: ZipFile, sourceCodePath: String): List[String] = {
    val zipTempDir = Files.createTempDirectory("plume-unzip-")
    try {
      Using.resource(zf) { (zip: ZipFile) =>
        // Copy zipped files across
        return moveClassFiles(
          zip
            .entries()
            .asScala
            .filter(f => !f.isDirectory && f.getName.endsWith(".class"))
            .flatMap(entry => {
              val sourceCodePathFile = new File(sourceCodePath)
              // Handle the case if the input source code path is an archive itself
              val destFile = if (sourceCodePathFile.isDirectory) {
                new File(zipTempDir.toAbsolutePath.toString + File.separator + entry.getName)
              } else {
                new File(zipTempDir.toAbsolutePath.toString + File.separator + entry.getName)
              }
              // dirName accounts for nested directories as a result of JAR package structure
              val dirName = destFile.getAbsolutePath
                .substring(0, destFile.getAbsolutePath.lastIndexOf(File.separator))
              // Create directory path
              new File(dirName).mkdirs()
              try {
                if (destFile.exists()) destFile.delete()
                Using.resource(zip.getInputStream(entry)) { input =>
                  Files.copy(input, destFile.toPath)
                }
                destFile.deleteOnExit()
                Option(destFile.getAbsolutePath)
              } catch {
                case e: Exception =>
                  logger
                    .warn(
                      s"Encountered an error while extracting entry ${entry.getName} from archive ${zip.getName}.",
                      e
                    )
                  Option.empty
              }
            })
            .toList
        )
      }
    } catch {
      case e: Exception => throw new RuntimeException(s"Error extracting files from archive at ${zf.getName}", e)
    } finally {
      FileUtils.deleteDirectory(zipTempDir.toFile)
    }
  }

  /** Retrieve parseable files from archive types.
    */
  def extractSourceFilesFromArchive(sourceCodeDir: String, archiveFileExtensions: Set[String]): List[String] = {
    val archives =
      if (
        new File(sourceCodeDir).isFile &&
        archiveFileExtensions.map(sourceCodeDir.endsWith).reduce((a, b) => a && b)
      ) {
        List(sourceCodeDir)
      } else {
        SourceFiles.determine(sourceCodeDir, archiveFileExtensions)
      }
    archives.flatMap { x => unzipArchive(new ZipFile(x), sourceCodeDir) }
  }

  /** Removes all files in the temporary unpacking directory.
    */
  def clean(): Unit = {
    FileUtils.deleteDirectory(getUnpackingDir.toFile)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy