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

de.fraunhofer.aisec.cpg.TranslationManager.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2021, Fraunhofer AISEC. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *                    $$$$$$\  $$$$$$$\   $$$$$$\
 *                   $$  __$$\ $$  __$$\ $$  __$$\
 *                   $$ /  \__|$$ |  $$ |$$ /  \__|
 *                   $$ |      $$$$$$$  |$$ |$$$$\
 *                   $$ |      $$  ____/ $$ |\_$$ |
 *                   $$ |  $$\ $$ |      $$ |  $$ |
 *                   \$$$$$   |$$ |      \$$$$$   |
 *                    \______/ \__|       \______/
 *
 */
package de.fraunhofer.aisec.cpg

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing
import de.fraunhofer.aisec.cpg.frontends.TranslationException
import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend
import de.fraunhofer.aisec.cpg.graph.Component
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.TypeManager
import de.fraunhofer.aisec.cpg.helpers.Benchmark
import de.fraunhofer.aisec.cpg.helpers.Util
import de.fraunhofer.aisec.cpg.passes.Pass
import java.io.File
import java.io.PrintWriter
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicBoolean
import java.util.stream.Collectors
import kotlin.reflect.full.findAnnotation
import org.slf4j.LoggerFactory

/** Main entry point for all source code translation for all language front-ends. */
class TranslationManager
private constructor(
    /**
     * Returns the current (immutable) configuration of this TranslationManager.
     *
     * @return the configuration
     */
    val config: TranslationConfiguration
) {
    private val isCancelled = AtomicBoolean(false)

    /**
     * Kicks off the analysis.
     *
     * This method orchestrates all passes that will do the main work.
     *
     * @return a [CompletableFuture] with the [TranslationResult].
     */
    fun analyze(): CompletableFuture {
        val result = TranslationResult(this, ScopeManager())

        // We wrap the analysis in a CompletableFuture, i.e. in an async task.
        return CompletableFuture.supplyAsync {
            val outerBench =
                Benchmark(
                    TranslationManager::class.java,
                    "Translation into full graph",
                    false,
                    result
                )
            val executedPasses = mutableSetOf()
            var executedFrontends = setOf()

            try {
                // Parse Java/C/CPP files
                var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result)
                executedFrontends = runFrontends(result, config)
                bench.addMeasurement()

                // Apply passes
                for (pass in config.registeredPasses) {
                    bench = Benchmark(pass.javaClass, "Executing Pass", false, result)
                    if (pass.runsWithCurrentFrontend(executedFrontends)) {
                        executedPasses.add(pass)
                        pass.accept(result)
                    }
                    bench.addMeasurement()
                    if (result.isCancelled) {
                        log.warn("Analysis interrupted, stopping Pass evaluation")
                    }
                }
            } catch (ex: TranslationException) {
                throw CompletionException(ex)
            } finally {
                outerBench.addMeasurement()
                if (!config.disableCleanup) {
                    log.debug("Cleaning up {} Passes", executedPasses.size)

                    executedPasses.forEach { it.cleanup() }

                    log.debug("Cleaning up {} Frontends", executedFrontends.size)

                    executedFrontends.forEach { it.cleanup() }
                    TypeManager.getInstance().cleanup()
                }
            }
            result
        }
    }

    val passes: List
        get() = config.registeredPasses

    fun isCancelled(): Boolean {
        return isCancelled.get()
    }

    /**
     * Parses all language files using the respective [LanguageFrontend] and creates the initial set
     * of AST nodes.
     *
     * @param result the translation result that is being mutated
     * @param config the translation configuration
     * @throws TranslationException if the language front-end runs into an error and
     *   [TranslationConfiguration.failOnError]
     * * is `true`.
     */
    @Throws(TranslationException::class)
    private fun runFrontends(
        result: TranslationResult,
        config: TranslationConfiguration,
    ): Set {
        val usedFrontends = mutableSetOf()
        for (sc in this.config.softwareComponents.keys) {
            val component = Component()
            component.name = Name(sc)
            result.addComponent(component)

            var sourceLocations: List = this.config.softwareComponents[sc]!!

            var useParallelFrontends = config.useParallelFrontends

            val list =
                sourceLocations.flatMap { file ->
                    if (file.isDirectory) {
                        Files.find(
                                file.toPath(),
                                999,
                                { _: Path?, fileAttr: BasicFileAttributes ->
                                    fileAttr.isRegularFile
                                }
                            )
                            .map { it.toFile() }
                            .collect(Collectors.toList())
                    } else {
                        val frontendClass = file.language?.frontend
                        val supportsParallelParsing =
                            file.language
                                ?.frontend
                                ?.findAnnotation()
                                ?.supported
                                ?: true
                        // By default, the frontends support parallel parsing. But the
                        // SupportsParallelParsing annotation can be set to false and force
                        // to disable it.
                        if (useParallelFrontends && !supportsParallelParsing) {
                            log.warn(
                                "Parallel frontends are not yet supported for the language frontend ${frontendClass?.simpleName}"
                            )
                            useParallelFrontends = false
                        }
                        listOf(file)
                    }
                }
            if (config.useUnityBuild) {
                val tmpFile = Files.createTempFile("compile", ".cpp").toFile()
                tmpFile.deleteOnExit()

                PrintWriter(tmpFile).use { writer ->
                    list.forEach {
                        if (CXXLanguageFrontend.CXX_EXTENSIONS.contains(Util.getExtension(it))) {
                            if (config.topLevel != null) {
                                val topLevel = config.topLevel.toPath()
                                writer.write(
                                    """
#include "${topLevel.relativize(it.toPath())}"

"""
                                        .trimIndent()
                                )
                            } else {
                                writer.write(
                                    """
#include "${it.absolutePath}"

"""
                                        .trimIndent()
                                )
                            }
                        }
                    }
                }

                sourceLocations = listOf(tmpFile)
                if (config.compilationDatabase != null) {
                    // merge include paths from all translation units
                    config.compilationDatabase.addIncludePath(
                        tmpFile,
                        config.compilationDatabase.allIncludePaths
                    )
                }
            } else {
                sourceLocations = list
            }

            TypeManager.setTypeSystemActive(config.typeSystemActiveInFrontend)

            usedFrontends.addAll(
                if (useParallelFrontends) {
                    parseParallel(component, result, sourceLocations)
                } else {
                    parseSequentially(component, result, sourceLocations)
                }
            )

            if (!config.typeSystemActiveInFrontend) {
                TypeManager.setTypeSystemActive(true)

                result.components.forEach { s ->
                    s.translationUnits.forEach {
                        val bench =
                            Benchmark(this.javaClass, "Activating types for ${it.name}", true)
                        result.scopeManager.activateTypes(it)
                        bench.stop()
                    }
                }
            }
        }

        return usedFrontends
    }

    private fun parseParallel(
        component: Component,
        result: TranslationResult,
        sourceLocations: Collection
    ): Set {
        val usedFrontends = mutableSetOf()

        log.info("Parallel parsing started")
        val futures = mutableListOf>>()
        val parallelScopeManagers = mutableListOf()

        val futureToFile: MutableMap>, File> =
            IdentityHashMap()

        for (sourceLocation in sourceLocations) {
            val scopeManager = ScopeManager()
            parallelScopeManagers.add(scopeManager)

            val future =
                CompletableFuture.supplyAsync {
                    try {
                        return@supplyAsync parse(component, scopeManager, sourceLocation)
                    } catch (e: TranslationException) {
                        throw RuntimeException("Error parsing $sourceLocation", e)
                    }
                }

            futures.add(future)
            futureToFile[future] = sourceLocation
        }

        for (future in futures) {
            try {
                future.get().ifPresent { f: LanguageFrontend ->
                    handleCompletion(result, usedFrontends, futureToFile[future], f)
                }
            } catch (e: InterruptedException) {
                log.error("Error parsing ${futureToFile[future]}", e)
                Thread.currentThread().interrupt()
            } catch (e: ExecutionException) {
                log.error("Error parsing ${futureToFile[future]}", e)
                Thread.currentThread().interrupt()
            }
        }

        // We want to merge everything into the final scope manager of the result
        result.scopeManager.mergeFrom(parallelScopeManagers)

        log.info("Parallel parsing completed")

        return usedFrontends
    }

    @Throws(TranslationException::class)
    private fun parseSequentially(
        component: Component,
        result: TranslationResult,
        sourceLocations: Collection
    ): Set {
        val usedFrontends = mutableSetOf()

        for (sourceLocation in sourceLocations) {
            log.info("Parsing {}", sourceLocation.absolutePath)

            parse(component, result.scopeManager, sourceLocation).ifPresent { f: LanguageFrontend ->
                handleCompletion(result, usedFrontends, sourceLocation, f)
            }
        }

        return usedFrontends
    }

    private fun handleCompletion(
        result: TranslationResult,
        usedFrontends: MutableSet,
        sourceLocation: File?,
        f: LanguageFrontend
    ) {
        usedFrontends.add(f)

        // remember which frontend parsed each file
        val sfToFe =
            result.scratch.computeIfAbsent(TranslationResult.SOURCE_LOCATIONS_TO_FRONTEND) {
                mutableMapOf()
            } as MutableMap
        sfToFe[sourceLocation!!.name] = f.javaClass.simpleName
    }

    @Throws(TranslationException::class)
    private fun parse(
        component: Component,
        scopeManager: ScopeManager,
        sourceLocation: File
    ): Optional {
        var frontend: LanguageFrontend? = null
        try {
            frontend = getFrontend(sourceLocation, scopeManager)

            if (frontend == null) {
                log.error("Found no parser frontend for ${sourceLocation.name}")

                if (config.failOnError) {
                    throw TranslationException(
                        "Found no parser frontend for ${sourceLocation.name}"
                    )
                }
                return Optional.empty()
            }
            component.translationUnits.add(frontend.parse(sourceLocation))
        } catch (ex: TranslationException) {
            log.error("An error occurred during parsing of ${sourceLocation.name}: ${ex.message}")
            if (config.failOnError) {
                throw ex
            }
        }
        return Optional.ofNullable(frontend)
    }

    private fun getFrontend(
        file: File,
        scopeManager: ScopeManager,
    ): LanguageFrontend? {
        val language = file.language

        return if (language != null) {
            try {
                language.newFrontend(config, scopeManager)
            } catch (e: Exception) {
                when (e) {
                    is InstantiationException,
                    is IllegalAccessException,
                    is InvocationTargetException,
                    is NoSuchMethodException -> {
                        log.error(
                            "Could not instantiate language frontend {}",
                            language.frontend.simpleName,
                            e
                        )
                        null
                    }
                    else -> throw e
                }
            }
        } else null
    }

    private val File.language: Language<*>?
        get() {
            return config.languages.firstOrNull { it.handlesFile(this) }
        }

    class Builder {
        private var config: TranslationConfiguration = TranslationConfiguration.builder().build()

        fun config(config: TranslationConfiguration): Builder {
            this.config = config
            return this
        }

        fun build(): TranslationManager {
            return TranslationManager(config)
        }
    }

    companion object {
        private val log = LoggerFactory.getLogger(TranslationManager::class.java)

        @JvmStatic
        fun builder(): Builder {
            return Builder()
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy