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

de.fraunhofer.aisec.cpg.frontends.golang.SpecificationHandler.kt Maven / Gradle / Ivy

There is a newer version: 8.3.0
Show newest version
/*
 * Copyright (c) 2023, 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.frontends.golang

import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.scopes.NameScope
import de.fraunhofer.aisec.cpg.helpers.Util

class SpecificationHandler(frontend: GoLanguageFrontend) :
    GoHandler(::ProblemDeclaration, frontend) {

    override fun handleNode(node: GoStandardLibrary.Ast.Spec): Declaration? {
        return when (node) {
            is GoStandardLibrary.Ast.ImportSpec -> handleImportSpec(node)
            is GoStandardLibrary.Ast.TypeSpec -> handleTypeSpec(node)
            is GoStandardLibrary.Ast.ValueSpec -> handleValueSpec(node)
            else -> {
                return handleNotSupported(node, node.goType)
            }
        }
    }

    private fun handleImportSpec(importSpec: GoStandardLibrary.Ast.ImportSpec): IncludeDeclaration {
        // We set the name of the include declaration to the imported name, i.e., the package name
        val name = importSpec.importName

        // We set the filename of the include declaration to the package path, i.e., its full path
        // including any module identifiers. This way we can match the include declaration back to
        // the namespace's path and name
        val filename = importSpec.path.value.removeSurrounding("\"")
        val include = newIncludeDeclaration(filename, rawNode = importSpec)
        include.name = parseName(name)

        val alias = importSpec.name?.name
        if (alias != null && alias != name) {
            val location = frontend.locationOf(importSpec)

            // If the name differs from the import name, we have an alias
            // Add an alias for the package on the current file
            location?.artifactLocation?.let {
                frontend.scopeManager.addAlias(it, Name(name), Name(alias))
            }
        }

        return include
    }

    private fun handleTypeSpec(spec: GoStandardLibrary.Ast.TypeSpec): Declaration {
        val type = spec.type
        val decl =
            when (type) {
                is GoStandardLibrary.Ast.StructType -> handleStructTypeSpec(spec, type)
                is GoStandardLibrary.Ast.InterfaceType -> handleInterfaceTypeSpec(spec, type)
                is GoStandardLibrary.Ast.FuncType -> handleFuncTypeSpec(spec, type)
                is GoStandardLibrary.Ast.Ident,
                is GoStandardLibrary.Ast.SelectorExpr,
                is GoStandardLibrary.Ast.MapType,
                is GoStandardLibrary.Ast.ArrayType,
                is GoStandardLibrary.Ast.StarExpr,
                is GoStandardLibrary.Ast.ChanType -> handleTypeDef(spec, type)
                else -> return ProblemDeclaration("not parsing type of type ${type.goType} yet")
            }

        return decl
    }

    private fun handleStructTypeSpec(
        typeSpec: GoStandardLibrary.Ast.TypeSpec,
        structType: GoStandardLibrary.Ast.StructType
    ): RecordDeclaration {
        val record = buildRecordDeclaration(structType, typeSpec.name.name, typeSpec)

        // Make sure to register the type
        frontend.typeManager.registerType(record.toType())

        return record
    }

    fun buildRecordDeclaration(
        structType: GoStandardLibrary.Ast.StructType,
        name: CharSequence,
        typeSpec: GoStandardLibrary.Ast.TypeSpec? = null,
    ): RecordDeclaration {
        val record = newRecordDeclaration(name, "struct", rawNode = typeSpec)

        frontend.scopeManager.enterScope(record)

        if (!structType.incomplete) {
            for (field in structType.fields.list) {
                val type = frontend.typeOf(field.type)

                // A field can also have no name, which means that it is embedded. In this case, it
                // can be accessed by the local name of its type and therefore we name the field
                // accordingly. We use the modifiers property to denote that this is an embedded
                // field, so we can easily retrieve them later
                val (fieldName, modifiers) =
                    if (field.names.isEmpty()) {
                        // Retrieve the root type local name
                        Pair(type.root.name.localName, listOf("embedded"))
                    } else {
                        Pair(field.names[0].name, listOf())
                    }

                val decl = newFieldDeclaration(fieldName, type, modifiers)
                frontend.setCodeAndLocation(decl, field)
                frontend.scopeManager.addDeclaration(decl)
            }
        }

        frontend.scopeManager.leaveScope(record)

        return record
    }

    private fun handleInterfaceTypeSpec(
        typeSpec: GoStandardLibrary.Ast.TypeSpec,
        interfaceType: GoStandardLibrary.Ast.InterfaceType
    ): Declaration {
        val record = newRecordDeclaration(typeSpec.name.name, "interface", rawNode = typeSpec)

        // Make sure to register the type
        frontend.typeManager.registerType(record.toType())

        frontend.scopeManager.enterScope(record)

        if (!interfaceType.incomplete) {
            for (field in interfaceType.methods.list) {
                val type = frontend.typeOf(field.type)

                // Even though this list is called "Methods", it contains all kinds
                // of things, so we need to proceed with caution. Only if the
                // "method" actually has a name, we declare a new method
                // declaration.
                if (field.names.isNotEmpty()) {
                    val method = newMethodDeclaration(field.names[0].name)
                    frontend.setCodeAndLocation(method, field)
                    method.type = type

                    frontend.scopeManager.enterScope(method)

                    val params = (field.type as? GoStandardLibrary.Ast.FuncType)?.params
                    if (params != null) {
                        frontend.declarationHandler.handleFuncParams(params)
                    }

                    frontend.scopeManager.leaveScope(method)

                    frontend.scopeManager.addDeclaration(method)
                } else {
                    log.debug("Adding {} as super class of interface {}", type.name, record.name)
                    // Otherwise, it contains either types or interfaces. For now, we
                    // hope that it only has interfaces. We consider embedded
                    // interfaces as sort of super types for this interface.
                    record.addSuperClass(type)
                }
            }
        }

        frontend.scopeManager.leaveScope(record)

        return record
    }

    /**
     * // handleValueSpec handles parsing of an ast.ValueSpec, which is a variable declaration.
     * Since this can potentially declare multiple variables with one "spec", this returns a
     * [DeclarationSequence].
     */
    private fun handleValueSpec(
        valueSpec: GoStandardLibrary.Ast.ValueSpec,
    ): Declaration {
        // Increment iota value
        frontend.declCtx.iotaValue++

        // If we only have one initializer on the right side and multiple ones on the left side,
        // we are deconstructing a tuple
        val lenValues = valueSpec.values.size
        if (lenValues == 1 && lenValues != valueSpec.names.size) {
            // We need to construct a "tuple" declaration on the left side that holds all the
            // variables
            val tuple = TupleDeclaration()
            tuple.type = autoType()

            for (ident in valueSpec.names) {
                // We want to make sure that top-level declarations, i.e, the ones that are directly
                // in a namespace are FQNs. Otherwise we cannot resolve them properly when we access
                // them outside of the package.
                val fqn =
                    if (frontend.scopeManager.currentScope is NameScope) {
                        fqn(ident.name)
                    } else {
                        ident.name
                    }
                val decl = newVariableDeclaration(fqn, rawNode = valueSpec)

                if (valueSpec.type != null) {
                    decl.type = frontend.typeOf(valueSpec.type!!)
                } else {
                    decl.type = autoType()
                }

                if (valueSpec.values.isNotEmpty()) {
                    tuple.initializer = frontend.expressionHandler.handle(valueSpec.values[0])
                }

                // We need to manually add the variables to the scope manager
                frontend.scopeManager.addDeclaration(decl)

                tuple += decl
            }
            return tuple
        } else {
            val sequence = DeclarationSequence()

            var type = valueSpec.type?.let { frontend.typeOf(it) }

            for ((nameIdx, ident) in valueSpec.names.withIndex()) {
                // We want to make sure that top-level declarations, i.e, the ones that are directly
                // in a namespace are FQNs. Otherwise we cannot resolve them properly when we access
                // them outside of the package.
                val fqn =
                    if (frontend.scopeManager.currentScope is NameScope) {
                        fqn(ident.name)
                    } else {
                        ident.name
                    }
                val decl = newVariableDeclaration(fqn, rawNode = valueSpec)
                if (type != null) {
                    decl.type = type
                } else {
                    decl.type = autoType()
                }

                if (valueSpec.values.size > nameIdx) {
                    // the initializer is in the "Values" slice with the respective index
                    decl.initializer = frontend.expressionHandler.handle(valueSpec.values[nameIdx])
                }

                // If we are in a const declaration, we need to do something rather unusual.
                // If we have an initializer, we need to set this as the current const initializer,
                // because following specs will "inherit" the one from the previous line.
                //
                // Note: we cannot just take the already parsed initializer, but instead we need to
                // reparse the raw AST expression, so that `iota` gets evaluated differently for
                // each spec
                if (frontend.declCtx.currentDecl?.tok == 64) {
                    var initializerExpr = valueSpec.values.getOrNull(nameIdx)
                    if (initializerExpr != null) {
                        // Set the current initializer
                        frontend.declCtx.constInitializers[nameIdx] = initializerExpr

                        // Set the const type
                        frontend.declCtx.constType = type
                    } else {
                        // Fetch expr from existing initializers
                        initializerExpr = frontend.declCtx.constInitializers[nameIdx]
                        if (initializerExpr == null) {
                            Util.errorWithFileLocation(
                                decl,
                                log,
                                "Const declaration is missing its initializer"
                            )
                        } else {
                            decl.initializer = frontend.expressionHandler.handle(initializerExpr)
                        }
                    }

                    type = frontend.declCtx.constType
                    if (type != null) {
                        decl.type = type
                    }
                }

                sequence += decl
            }

            return sequence
        }
    }

    private fun handleFuncTypeSpec(
        spec: GoStandardLibrary.Ast.TypeSpec,
        type: GoStandardLibrary.Ast.FuncType
    ): Declaration {
        // We model function types as typedef's, so that we can resolve it later
        val funcType = frontend.typeOf(type)
        val typedef = newTypedefDeclaration(funcType, frontend.typeOf(spec.name), rawNode = spec)

        frontend.scopeManager.addTypedef(typedef)

        return typedef
    }

    private fun handleTypeDef(
        spec: GoStandardLibrary.Ast.TypeSpec,
        type: GoStandardLibrary.Ast.Expr
    ): Declaration {
        val targetType = frontend.typeOf(type)

        // We need to return either a type alias or a new type. See
        // https://go.dev/ref/spec#Type_identity
        return when {
            // When we have an assignment, we have *identical* types. We handle them as a typedef /
            // alias.
            spec.assign != 0 -> {
                val aliasType = frontend.typeOf(spec.name)
                val typedef = newTypedefDeclaration(targetType, aliasType, rawNode = spec)

                frontend.scopeManager.addTypedef(typedef)
                typedef
            }
            // Otherwise, we are creating a new type, which is *different*. Since Go allows to add
            // methods to these kind of types, we need to create them as a record declaration. We
            // use the special kind "overlay" to identity such types and put the target type in the
            // list of superclasses.
            else -> {
                val record = newRecordDeclaration(spec.name.name, "overlay")

                // We add the underlying type as the single super class
                record.superClasses = mutableListOf(targetType)

                // Register the type with the type system
                frontend.typeManager.registerType(record.toType())
                record
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy