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

de.fraunhofer.aisec.cpg.frontends.cxx.DeclaratorHandler.kt Maven / Gradle / Ivy

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

import de.fraunhofer.aisec.cpg.ResolveInFrontend
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.graph.scopes.RecordScope
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.helpers.Util
import java.util.*
import java.util.function.Supplier
import java.util.regex.Pattern
import java.util.stream.Collectors
import org.eclipse.cdt.core.dom.ast.*
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier
import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage
import org.eclipse.cdt.internal.core.dom.parser.cpp.*

/**
 * Takes care of translating a
 * [declarator](https://en.cppreference.com/w/cpp/language/declarations#Declarators) into a
 * [Declaration].
 *
 * See [DeclarationHandler] for a detailed explanation, why this is split into a dedicated handler.
 */
class DeclaratorHandler(lang: CXXLanguageFrontend) :
    CXXHandler(Supplier(::ProblemDeclaration), lang) {

    override fun handleNode(node: IASTNameOwner): Declaration {
        return when (node) {
            is CPPASTFunctionDeclarator -> handleCPPFunctionDeclarator(node)
            is IASTStandardFunctionDeclarator -> handleFunctionDeclarator(node)
            is IASTFieldDeclarator -> handleFieldDeclarator(node)
            is IASTDeclarator -> handleDeclarator(node)
            is IASTCompositeTypeSpecifier -> handleCompositeTypeSpecifier(node)
            is CPPASTSimpleTypeTemplateParameter -> handleTemplateTypeParameter(node)
            else -> {
                return handleNotSupported(node, node.javaClass.name)
            }
        }
    }

    /**
     * The [CPPASTFunctionDeclarator] extends the [IASTStandardFunctionDeclarator] and has some more
     * attributes which we want to consider. Currently, this is the
     * [CPPASTFunctionDeclarator.trailingReturnType] which will be added to the FunctionDeclaration.
     * This represents the return-type of a lambda function.
     */
    private fun handleCPPFunctionDeclarator(node: CPPASTFunctionDeclarator): Declaration {
        // Handle it as a regular C function first
        val function = handleFunctionDeclarator(node)

        // If we have a trailing return type, we specify the return type of the (lambda) function
        if (function is FunctionDeclaration && node.trailingReturnType != null) {
            function.returnTypes = listOf(frontend.typeOf(node.trailingReturnType))
        }

        return function
    }

    /**
     * This is sort of a catch-all function, if none of the previous specialized declarators match.
     * It can be one of three things:
     * - a wrapper around a nested declarator, in which case we delegate the handling to the nested
     *   one,
     * - a field declaration, if this declaration occurs within a class or has a qualified name, or
     * - a variable declaration in all the other cases.
     */
    private fun handleDeclarator(ctx: IASTDeclarator): Declaration {
        // This is just a nested declarator, i.e. () wrapping the real declarator
        if (ctx.initializer == null && ctx.nestedDeclarator is IASTDeclarator) {
            return handle(ctx.nestedDeclarator)
                ?: ProblemDeclaration("could not parse nested declaration")
        }

        val name = ctx.name.toString()

        // Check, if the name is qualified or if we are within a record scope
        return if (
            (frontend.scopeManager.currentScope is RecordScope ||
                name.contains(language.namespaceDelimiter))
        ) {
            // If yes, treat this like a field declaration
            this.handleFieldDeclarator(ctx)
        } else {
            // If not, this is a normal variable declaration. First, we need to check if this
            // declaration allows to have an implicit constructor initializer. Only C++ has
            // constructors and thus implicit (constructor) initialization calls
            val implicitInitializerAllowed = frontend.dialect is GPPLanguage

            val declaration =
                newVariableDeclaration(
                    ctx.name.toString(),
                    newUnknownType(), // Type will be filled out later by
                    // handleSimpleDeclaration
                    ctx.rawSignature,
                    implicitInitializerAllowed,
                )

            // Parse the initializer, if we have one
            val init = ctx.initializer
            if (init != null) {
                declaration.initializer = frontend.initializerHandler.handle(init)
            }

            // Add this declaration to the current scope
            frontend.scopeManager.addDeclaration(declaration)

            declaration
        }
    }

    /**
     * Translates (data members)[https://en.cppreference.com/w/cpp/language/data_members] of a C++
     * class or C/C++ struct into a [FieldDeclaration].
     */
    private fun handleFieldDeclarator(ctx: IASTDeclarator): FieldDeclaration {
        val initializer = ctx.initializer?.let { frontend.initializerHandler.handle(it) }

        val name = ctx.name.toString()

        val declaration =
            if (name.contains(language.namespaceDelimiter)) {
                val rr = name.split(language.namespaceDelimiter).toTypedArray()
                val fieldName = rr[rr.size - 1]
                newFieldDeclaration(
                    fieldName,
                    newUnknownType(),
                    emptyList(),
                    ctx.rawSignature,
                    frontend.getLocationFromRawNode(ctx),
                    initializer,
                    true
                )
            } else {
                newFieldDeclaration(
                    name,
                    newUnknownType(),
                    emptyList(),
                    ctx.rawSignature,
                    frontend.getLocationFromRawNode(ctx),
                    initializer,
                    true
                )
            }

        frontend.scopeManager.addDeclaration(declaration)

        return declaration
    }

    /**
     * A small utility function that creates a [ConstructorDeclaration], [MethodDeclaration] or
     * [FunctionDeclaration] depending on which scope the function should live in. This basically
     * checks if the scope is a namespace or a record and if the name matches to the record (in case
     * of a constructor).
     */
    private fun createFunctionOrMethodOrConstructor(
        name: Name,
        scope: Scope?,
        ctx: IASTNode,
    ): FunctionDeclaration {
        // Retrieve the AST node for the scope we need to put the function in
        val holder = scope?.astNode

        // Check, if it's a constructor. This is the case if the local names of the function and the
        // record declaration match
        val func =
            if (holder is RecordDeclaration && name.localName == holder.name.localName) {
                newConstructorDeclaration(name, null, holder, ctx)
            } else if (scope?.astNode is NamespaceDeclaration) {
                // It could also be a scoped function declaration.
                newFunctionDeclaration(name, null, ctx)
            } else {
                // Otherwise, it's a method to a known or unknown record
                newMethodDeclaration(name, null, false, holder as? RecordDeclaration, ctx)
            }

        // Also make sure to correctly set the scope of the function, regardless where we are in the
        // AST currently
        func.scope = scope

        return func
    }

    @ResolveInFrontend("lookupScope")
    private fun handleFunctionDeclarator(ctx: IASTStandardFunctionDeclarator): ValueDeclaration {
        // Programmers can wrap the function name in as many levels of parentheses as they like. CDT
        // treats these levels as separate declarators, so we need to get to the bottom for the
        // actual name using the realName extension function.
        val (nameDecl: IASTDeclarator, hasPointer) = ctx.realName()
        var name =
            if (nameDecl.name == null) {
                Name("")
            } else {
                parseName(nameDecl.name.toString())
            }

        // Attention! This might actually be a function pointer (requires at least one level of
        // parentheses and a pointer operator)
        if (nameDecl !== ctx && hasPointer) {
            return handleFunctionPointer(ctx, name.toString())
        }

        /*
         * As always, there are some special cases to consider and one of those are C++ operators.
         * They are regarded as functions and eclipse CDT for some reason introduces a whitespace in the function name, which will complicate things later on
         */
        if (name.startsWith("operator")) {
            name = name.replace(" ", "")
        }
        val declaration: FunctionDeclaration

        // We need to check if this function is actually part of a named declaration, such as a
        // record or a namespace, but defined externally.
        var parentScope: NameScope? = null

        // Check for function definitions that really belong to a named scoped, i.e. if they
        // contain a scope operator. This could either be a namespace or a record.
        val parent = name.parent
        if (parent != null) {
            // In this case, the name contains a qualifier, and we can try to check, if we have a
            // matching name scope for the parent name
            parentScope = frontend.scopeManager.lookupScope(parent.toString())

            declaration = createFunctionOrMethodOrConstructor(name, parentScope, ctx.parent)
        } else if (frontend.scopeManager.isInRecord) {
            // If the current scope is already a record, it's a method
            declaration =
                createFunctionOrMethodOrConstructor(
                    name,
                    frontend.scopeManager.currentScope,
                    ctx.parent
                )
        } else {
            // a plain old function, outside any named scope
            declaration = newFunctionDeclaration(name, null, ctx.parent)
        }

        // We want to determine, whether we are currently outside a named scope on the AST
        val outsideOfScope = frontend.scopeManager.currentScope != declaration.scope

        // If we know our parent scope, but are outside the actual scope on the AST, we
        // need to temporarily enter the scope. This way, we can do a little trick
        // and (manually) add the declaration to the AST element of the current scope
        // (probably the global scope), but associate it to the named scope.
        if (parentScope != null && outsideOfScope) {
            // Bypass the scope manager and manually add it to the AST parent
            val scopeParent = frontend.scopeManager.currentScope?.astNode
            if (scopeParent != null && scopeParent is DeclarationHolder) {
                scopeParent.addDeclaration(declaration)
            }

            // Enter the record scope
            parentScope.astNode?.let { frontend.scopeManager.enterScope(it) }

            // We also need to by-pass the scope manager for this, because it will
            // otherwise add the declaration to the AST element of the named scope (the record
            // or namespace declaration); in the case of a record declaration to the `methods`
            // fields. However, since `methods` is an
            // AST field, (for now) we only want those methods in there, that were actual AST
            // parents. This is also something that we need to figure out how we want to handle
            // this.
            parentScope.valueDeclarations.add(declaration)
        } else {
            // Add the declaration via the scope manager
            frontend.scopeManager.addDeclaration(declaration)
        }

        // Enter the scope of the function itself
        frontend.scopeManager.enterScope(declaration)

        // Create the method receiver (if this is a method)
        if (declaration is MethodDeclaration) {
            createMethodReceiver(declaration)
        }

        var i = 0
        for (param in ctx.parameters) {
            val arg = frontend.parameterDeclarationHandler.handle(param)

            if (arg is ParamVariableDeclaration) {
                // check for void type parameters
                if (arg.type is IncompleteType) {
                    if (arg.name.isNotEmpty()) {
                        Util.warnWithFileLocation(
                            declaration,
                            log,
                            "Named parameter cannot have void type"
                        )
                    } else {
                        // specifying void as first parameter is ok and means that the function has
                        // no parameters
                        if (i == 0) {
                            continue
                        } else {
                            Util.warnWithFileLocation(
                                declaration,
                                log,
                                "void parameter must be the first and only parameter"
                            )
                        }
                    }
                }

                arg.argumentIndex = i
            }
            // Note that this .addValueDeclaration call already adds arg to the function's
            // parameters.
            // This is why the following line has been commented out by @KW
            frontend.scopeManager.addDeclaration(arg)
            // declaration.getParameters().add(arg);
            i++
        }

        // Check for varargs. Note the difference to Java: here, we don't have a named array
        // containing the varargs, but they are rather treated as kind of an invisible arg list that
        // is appended to the original ones. For coherent graph behaviour, we introduce an implicit
        // declaration that wraps this list
        if (ctx.takesVarArgs()) {
            val varargs = newParamVariableDeclaration("va_args", newUnknownType(), true, "")
            varargs.isImplicit = true
            varargs.argumentIndex = i
            frontend.scopeManager.addDeclaration(varargs)
        }
        frontend.scopeManager.leaveScope(declaration)

        // if we know our record declaration, but are outside the actual record, we
        // need to leave the record scope again afterwards
        if (parentScope != null && outsideOfScope) {
            parentScope.astNode?.let { frontend.scopeManager.leaveScope(it) }
        }

        // We recognize an ambiguity here, but cannot solve it at the moment
        if (
            name.isNotEmpty() &&
                ctx.parent is CPPASTDeclarator &&
                declaration.body == null &&
                frontend.scopeManager.currentFunction != null
        ) {
            val problem =
                newProblemDeclaration(
                    "CDT tells us this is a (named) function declaration in parenthesis without a body directly within a block scope, this might be an ambiguity which we cannot solve currently."
                )

            Util.warnWithFileLocation(frontend, ctx, log, problem.problem)

            return problem
        }

        return declaration
    }

    /**
     * This function takes cares of creating a receiver and setting it to the supplied
     * [MethodDeclaration]. In C++ this is called the
     * [implicit object parameter](https://en.cppreference.com/w/cpp/language/overload_resolution#Implicit_object_parameter)
     * .
     */
    private fun createMethodReceiver(declaration: MethodDeclaration) {
        val recordDeclaration = declaration.recordDeclaration

        // Create a pointer to the class type (if we know it)
        val type =
            recordDeclaration?.toType()?.reference(PointerType.PointerOrigin.POINTER)
                ?: newUnknownType()

        // Create the receiver. implicitInitializerAllowed must be false, otherwise fixInitializers
        // will create another implicit constructexpression for this variable, and we don't want
        // this.
        val thisDeclaration =
            newVariableDeclaration("this", type = type, implicitInitializerAllowed = false)
        // Yes, this is implicit
        thisDeclaration.isImplicit = true

        // Add it to the scope of the method
        frontend.scopeManager.addDeclaration(thisDeclaration)

        // We need to manually set the receiver, since the scope manager cannot figure this out
        declaration.receiver = thisDeclaration
    }

    private fun handleFunctionPointer(ctx: IASTFunctionDeclarator, name: String): ValueDeclaration {
        val initializer =
            if (ctx.initializer == null) null
            else frontend.initializerHandler.handle(ctx.initializer)
        // unfortunately we are not told whether this is a field or not, so we have to find it out
        // ourselves
        val result: ValueDeclaration
        val recordDeclaration = frontend.scopeManager.currentRecord
        if (recordDeclaration == null) {
            // variable
            result = newVariableDeclaration(name, newUnknownType(), ctx.rawSignature, true)
            result.initializer = initializer
        } else {
            // field
            val code = ctx.rawSignature
            val namePattern = Pattern.compile("\\((\\*|.+\\*)(?[^)]*)")
            val matcher = namePattern.matcher(code)
            var fieldName: String? = ""
            if (matcher.find()) {
                fieldName = matcher.group("name").trim()
            }
            result =
                newFieldDeclaration(
                    fieldName,
                    newUnknownType(),
                    emptyList(),
                    code,
                    frontend.getLocationFromRawNode(ctx),
                    initializer,
                    true
                )
        }

        result.location = frontend.getLocationFromRawNode(ctx)
        frontend.scopeManager.addDeclaration(result)
        return result
    }

    private fun handleCompositeTypeSpecifier(ctx: IASTCompositeTypeSpecifier): RecordDeclaration {
        val kind: String =
            when (ctx.key) {
                IASTCompositeTypeSpecifier.k_struct -> "struct"
                IASTCompositeTypeSpecifier.k_union -> "union"
                ICPPASTCompositeTypeSpecifier.k_class -> "class"
                else -> "struct"
            }

        val recordDeclaration =
            newRecordDeclaration(
                ctx.name.toString(),
                kind,
                ctx.rawSignature,
            )

        // Handle c++ classes
        if (ctx is CPPASTCompositeTypeSpecifier) {
            recordDeclaration.superClasses =
                Arrays.stream(ctx.baseSpecifiers)
                    .map { b: ICPPASTBaseSpecifier ->
                        TypeParser.createFrom(b.nameSpecifier.toString(), true, frontend)
                    }
                    .collect(Collectors.toList())
        }

        frontend.scopeManager.addDeclaration(recordDeclaration)

        frontend.scopeManager.enterScope(recordDeclaration)

        processMembers(ctx)

        if (recordDeclaration.constructors.isEmpty()) {
            val constructorDeclaration =
                newConstructorDeclaration(
                    recordDeclaration.name.localName,
                    recordDeclaration.name.toString(),
                    recordDeclaration
                )

            createMethodReceiver(constructorDeclaration)

            // set this as implicit
            constructorDeclaration.isImplicit = true

            // and set the type, constructors always have implicitly the return type of their class
            constructorDeclaration.type = FunctionType.computeType(constructorDeclaration)
            recordDeclaration.addConstructor(constructorDeclaration)
            frontend.scopeManager.addDeclaration(constructorDeclaration)
        }

        frontend.scopeManager.leaveScope(recordDeclaration)

        return recordDeclaration
    }

    /**
     * Handles template parameters that are types
     *
     * @param ctx
     * @return TypeParamDeclaration with its name
     */
    private fun handleTemplateTypeParameter(
        ctx: CPPASTSimpleTypeTemplateParameter
    ): TypeParamDeclaration {
        return newTypeParamDeclaration(ctx.rawSignature, ctx.rawSignature, ctx)
    }

    private fun processMembers(ctx: IASTCompositeTypeSpecifier) {
        for (member in ctx.members) {
            if (member is CPPASTVisibilityLabel) {
                // TODO: parse visibility
                continue
            }

            frontend.declarationHandler.handle(member)
        }
    }
}

/**
 * This function returns the real name (declarator) of this [IASTDeclarator]. The name itself can be
 * wrapped in many layers of nested declarators, e.g., if the name is wrapped in ().
 */
fun IASTDeclarator.realName(): Pair {
    var nameDecl: IASTDeclarator = this
    var hasPointer = false
    while (nameDecl.nestedDeclarator != null) {
        nameDecl = nameDecl.nestedDeclarator
        if (nameDecl.pointerOperators.isNotEmpty()) {
            hasPointer = true
        }
    }
    return Pair(nameDecl, hasPointer)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy