From 6eee05ba3ea396af8ad3c160b6a660a1c08e35e0 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 28 Mar 2024 22:07:43 +0100 Subject: [PATCH] Converted Go frontend --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 1 - .../cpg/analysis/fsm/DFAOrderEvaluator.kt | 1 - .../fraunhofer/aisec/cpg/frontends/Handler.kt | 1 + .../aisec/cpg/graph/ExpressionBuilder.kt | 4 - .../fraunhofer/aisec/cpg/graph/Extensions.kt | 1 - .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 12 + .../aisec/cpg/graph/builder/Fluent.kt | 24 +- .../graph/statements/DeclarationStatement.kt | 4 + .../aisec/cpg/passes/EdgeCachePass.kt | 5 - .../frontends/golang/DeclarationHandler.kt | 106 ++--- .../cpg/frontends/golang/ExpressionHandler.kt | 220 +++++----- .../aisec/cpg/frontends/golang/GoHandler.kt | 5 + .../frontends/golang/GoLanguageFrontend.kt | 87 ++-- .../frontends/golang/SpecificationHandler.kt | 246 ++++++----- .../cpg/frontends/golang/StatementHandler.kt | 404 ++++++++---------- 15 files changed, 526 insertions(+), 595 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 56fb4c9348..c211cd3082 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.passes.EdgeCachePass -import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt index a5467d553c..9d7f852835 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference -import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index ba8e0871ad..f356cd1db0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.helpers.Util.errorWithFileLocation import java.lang.reflect.ParameterizedType import java.lang.reflect.Type diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index f2eb9f11bf..bc97f20165 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -122,15 +122,11 @@ fun MetadataProvider.newUnaryOperator( @JvmOverloads fun MetadataProvider.newAssignExpression( operatorCode: String = "=", - lhs: List = listOf(), - rhs: List = listOf(), rawNode: Any? = null ): AssignExpression { val node = AssignExpression() node.applyMetadata(this, operatorCode, rawNode, true) node.operatorCode = operatorCode - node.lhs = lhs - node.rhs = rhs log(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 996897b198..b6638218a8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -37,7 +37,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass -import de.fraunhofer.aisec.cpg.passes.astParent /** * Flattens the AST beginning with this node and returns all nodes of type [T]. For convenience, an diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 9b51c15f61..3a9b4017a7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.LOGGER import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -421,3 +422,14 @@ fun T.withChildren( return this } + +context(ContextProvider) +fun T.declare(): T { + val scopeManager = + this@ContextProvider.ctx?.scopeManager + ?: throw TranslationException( + "Trying to create node children without a ContextProvider. This will fail." + ) + scopeManager.addDeclaration(this) + return this +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index a34c96670b..ecb4955e45 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1088,7 +1088,11 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator { */ context(LanguageFrontend<*, *>, StatementHolder) operator fun Expression.plusAssign(rhs: Expression) { - val node = (this@LanguageFrontend).newAssignExpression("+=", listOf(this), listOf(rhs)) + val node = + (this@LanguageFrontend).newAssignExpression("+=").withChildren { + it.rhs = listOf(rhs) + it.lhs = listOf(this) + } (this@StatementHolder) += node } @@ -1328,7 +1332,11 @@ infix fun Expression.assign(init: AssignExpression.() -> Expression): AssignExpr */ context(LanguageFrontend<*, *>, Holder) infix fun Expression.assign(rhs: Expression): AssignExpression { - val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this), listOf(rhs)) + val node = + (this@LanguageFrontend).newAssignExpression("=").withChildren { + it.rhs = listOf(rhs) + it.lhs = listOf(this) + } if (this@Holder is StatementHolder) { this@Holder += node @@ -1343,7 +1351,11 @@ infix fun Expression.assign(rhs: Expression): AssignExpression { */ context(LanguageFrontend<*, *>, Holder) infix fun Expression.assignPlus(rhs: Expression): AssignExpression { - val node = (this@LanguageFrontend).newAssignExpression("+=", listOf(this), listOf(rhs)) + val node = + (this@LanguageFrontend).newAssignExpression("+=").withChildren { + it.rhs = listOf(rhs) + it.lhs = listOf(this) + } if (this@Holder is StatementHolder) { this@Holder += node @@ -1358,7 +1370,11 @@ infix fun Expression.assignPlus(rhs: Expression): AssignExpression { */ context(LanguageFrontend<*, *>, Holder) infix fun Expression.assignAsExpr(rhs: Expression): AssignExpression { - val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this), listOf(rhs)) + val node = + (this@LanguageFrontend).newAssignExpression("=").withChildren { + it.rhs = listOf(rhs) + it.lhs = listOf(this) + } node.usedAsExpression = true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DeclarationStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DeclarationStatement.kt index 5dc54d9509..1d01395ee5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DeclarationStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DeclarationStatement.kt @@ -92,4 +92,8 @@ open class DeclarationStatement : Statement() { } override fun hashCode() = Objects.hash(super.hashCode(), declarations) + + operator fun plusAssign(declaration: Declaration) { + addToPropertyEdgeDeclaration(declaration) + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt index 12aa17edd0..8da5b56387 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt @@ -143,8 +143,3 @@ class EdgeCachePass(ctx: TranslationContext) : ComponentPass(ctx) { // nothing to do } } - -val Node.astParent: Node? - get() { - return Edges.to(this, EdgeType.AST).firstOrNull()?.source - } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt index 50dd9acbc9..741761dbe8 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt @@ -63,12 +63,14 @@ class DeclarationHandler(frontend: GoLanguageFrontend) : // because the syntax is only there to ensure that this method is part // of the struct, but it is not modifying the receiver. if (recvField?.names?.isNotEmpty() == true) { - method.receiver = - newVariableDeclaration( - recvField.names[0].name, - recordType, - rawNode = recvField - ) + method.withChildren { + it.receiver = + newVariableDeclaration( + recvField.names[0].name, + recordType, + rawNode = recvField + ) + } } if (recordType !is UnknownType) { @@ -103,60 +105,58 @@ class DeclarationHandler(frontend: GoLanguageFrontend) : newFunctionDeclaration(funcDecl.name.name, localNameOnly, rawNode = funcDecl) } - frontend.scopeManager.enterScope(func) - - if (func is MethodDeclaration && func.receiver != null) { - // Add the receiver do the scope manager, so we can resolve the receiver value - frontend.scopeManager.addDeclaration(func.receiver) - } + func.withChildren(hasScope = true) { + if (func is MethodDeclaration && func.receiver != null) { + // Add the receiver do the scope manager, so we can resolve the receiver value + func.receiver?.declare() + } - val returnTypes = mutableListOf() - - // Build return types (and variables) - val results = funcDecl.type.results - if (results != null) { - for (returnVar in results.list) { - returnTypes += frontend.typeOf(returnVar.type) - - // If the function has named return variables, be sure to declare them as well - if (returnVar.names.isNotEmpty()) { - val param = - newVariableDeclaration( - returnVar.names[0].name, - frontend.typeOf(returnVar.type), - rawNode = returnVar - ) - - // Add parameter to scope - frontend.scopeManager.addDeclaration(param) + val returnTypes = mutableListOf() + + // Build return types (and variables) + val results = funcDecl.type.results + if (results != null) { + for (returnVar in results.list) { + returnTypes += frontend.typeOf(returnVar.type) + + // If the function has named return variables, be sure to declare them as well + if (returnVar.names.isNotEmpty()) { + val param = + newVariableDeclaration( + returnVar.names[0].name, + frontend.typeOf(returnVar.type), + rawNode = returnVar + ) + + // Add parameter to scope + param.declare() + } } } - } - func.type = frontend.typeOf(funcDecl.type) - func.returnTypes = returnTypes - - // Parse parameters - handleFuncParams(funcDecl.type.params) - - // Only parse function body in non-dependencies - if (!frontend.isDependency) { - // Check, if the last statement is a return statement, otherwise we insert an implicit - // one - val body = funcDecl.body?.let { frontend.statementHandler.handle(it) } - if (body is Block) { - val last = body.statements.lastOrNull() - if (last !is ReturnStatement) { - val ret = newReturnStatement() - ret.isImplicit = true - body += ret - } + func.type = frontend.typeOf(funcDecl.type) + func.returnTypes = returnTypes + + // Parse parameters + handleFuncParams(funcDecl.type.params) + + // Only parse function body in non-dependencies + if (!frontend.isDependency) { + // Check, if the last statement is a return statement, otherwise we insert an + // implicit one + func.body = + (funcDecl.body?.let { frontend.statementHandler.handle(it) } as? Block) + ?.withChildren { body -> + val last = body.statements.lastOrNull() + if (last !is ReturnStatement) { + val ret = newReturnStatement() + ret.isImplicit = true + body += ret + } + } } - func.body = body } - frontend.scopeManager.leaveScope(func) - // Leave scope of record, if applicable (func as? MethodDeclaration)?.recordDeclaration?.let { frontend.scopeManager.leaveScope(it) diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt index 8a389e1a58..6ce68390ec 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -124,9 +124,11 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : } private fun handleBinaryExpr(binaryExpr: GoStandardLibrary.Ast.BinaryExpr): BinaryOperator { - val binOp = newBinaryOperator(binaryExpr.opString, rawNode = binaryExpr) - binOp.lhs = handle(binaryExpr.x) - binOp.rhs = handle(binaryExpr.y) + val binOp = + newBinaryOperator(binaryExpr.opString, rawNode = binaryExpr).withChildren { + it.lhs = handle(binaryExpr.x) + it.rhs = handle(binaryExpr.y) + } return binOp } @@ -165,9 +167,11 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : } private fun handleIndexExpr(indexExpr: GoStandardLibrary.Ast.IndexExpr): SubscriptExpression { - val ase = newSubscriptExpression(rawNode = indexExpr) - ase.arrayExpression = frontend.expressionHandler.handle(indexExpr.x) - ase.subscriptExpression = frontend.expressionHandler.handle(indexExpr.index) + val ase = + newSubscriptExpression(rawNode = indexExpr).withChildren { + it.arrayExpression = frontend.expressionHandler.handle(indexExpr.x) + it.subscriptExpression = frontend.expressionHandler.handle(indexExpr.index) + } return ase } @@ -182,14 +186,13 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : is GoStandardLibrary.Ast.InterfaceType, is GoStandardLibrary.Ast.StructType, is GoStandardLibrary.Ast.MapType, -> { - val cast = newCastExpression(rawNode = callExpr) - cast.castType = frontend.typeOf(unwrapped) + return newCastExpression(rawNode = callExpr).withChildren { + it.castType = frontend.typeOf(unwrapped) - if (callExpr.args.isNotEmpty()) { - cast.expression = frontend.expressionHandler.handle(callExpr.args[0]) + if (callExpr.args.isNotEmpty()) { + it.expression = frontend.expressionHandler.handle(callExpr.args[0]) + } } - - return cast } } @@ -222,29 +225,28 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : } // Differentiate between calls and member calls based on the callee - val call = - if (callee is MemberExpression) { + return if (callee is MemberExpression) { newMemberCallExpression(callee, rawNode = callExpr) } else { newCallExpression(callee, name, rawNode = callExpr) } - call.type = unknownType() - - // TODO(oxisto) Add type constraints - if (typeConstraints.isNotEmpty()) { - log.debug( - "Call {} has type constraints ({}), but we cannot add them to the call expression yet", - call.name, - typeConstraints.joinToString(", ") { it.name } - ) - } - - // Parse and add call arguments - for (arg in callExpr.args) { - call += handle(arg) - } + .withChildren { + it.type = unknownType() + + // TODO(oxisto) Add type constraints + if (typeConstraints.isNotEmpty()) { + log.debug( + "Call {} has type constraints ({}), but we cannot add them to the call expression yet", + it.name, + typeConstraints.joinToString(", ") { it.name } + ) + } - return call + // Parse and add call arguments + for (arg in callExpr.args) { + it += handle(arg) + } + } } /** @@ -274,21 +276,19 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : return newProblemExpression("could not create NewExpression with empty arguments") } - val n = newNewExpression(rawNode = callExpr) - - // First argument is type - val type = frontend.typeOf(callExpr.args[0]) - - // new is a pointer, so need to reference the type with a pointer - n.type = type.reference(PointerType.PointerOrigin.POINTER) + return newNewExpression(rawNode = callExpr).withChildren { + // First argument is type + val type = frontend.typeOf(callExpr.args[0]) - // a new expression also needs an initializer, which is usually a ConstructExpression - val construct = newConstructExpression(rawNode = callExpr) - construct.type = type + // new is a pointer, so need to reference the type with a pointer + it.type = type.reference(PointerType.PointerOrigin.POINTER) - n.initializer = construct + // a new expression also needs an initializer, which is usually a ConstructExpression + val construct = newConstructExpression(rawNode = callExpr) + construct.type = type - return n + it.initializer = construct + } } private fun handleMakeExpr(callExpr: GoStandardLibrary.Ast.CallExpr): Expression { @@ -301,24 +301,21 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : val expression = // Actually make() can make more than just arrays, i.e. channels and maps if (args[0] is GoStandardLibrary.Ast.ArrayType) { - val array = newNewArrayExpression(rawNode = callExpr) - - // second argument is a dimension (if this is an array), usually a literal - if (args.size > 1) { - array.addDimension(handle(args[1])) + newNewArrayExpression(rawNode = callExpr).withChildren { + // second argument is a dimension (if this is an array), usually a literal + if (args.size > 1) { + it.addDimension(handle(args[1])) + } } - array } else { // Create at least a generic construct expression for the given map or channel type // and provide the remaining arguments - val construct = newConstructExpression(rawNode = callExpr) - - // Pass the remaining arguments - for (expr in args.subList(1.coerceAtMost(args.size - 1), args.size - 1)) { - handle(expr).let { construct += it } + newConstructExpression(rawNode = callExpr).withChildren { construct -> + // Pass the remaining arguments + for (expr in args.subList(1.coerceAtMost(args.size - 1), args.size - 1)) { + handle(expr).let { construct += it } + } } - - construct } // First argument is always the type @@ -370,26 +367,23 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : * as its subscriptExpression to share some code between this and an index expression. */ private fun handleSliceExpr(sliceExpr: GoStandardLibrary.Ast.SliceExpr): SubscriptExpression { - val ase = newSubscriptExpression(rawNode = sliceExpr) - ase.arrayExpression = frontend.expressionHandler.handle(sliceExpr.x) - - // Build the slice expression - val range = newRangeExpression(rawNode = sliceExpr) - sliceExpr.low?.let { range.floor = frontend.expressionHandler.handle(it) } - sliceExpr.high?.let { range.ceiling = frontend.expressionHandler.handle(it) } - sliceExpr.max?.let { range.third = frontend.expressionHandler.handle(it) } - - ase.subscriptExpression = range - - return ase + return newSubscriptExpression(rawNode = sliceExpr).withChildren { ase -> + ase.arrayExpression = frontend.expressionHandler.handle(sliceExpr.x) + + // Build the slice expression + ase.subscriptExpression = + newRangeExpression(rawNode = sliceExpr).withChildren { range -> + sliceExpr.low?.let { range.floor = frontend.expressionHandler.handle(it) } + sliceExpr.high?.let { range.ceiling = frontend.expressionHandler.handle(it) } + sliceExpr.max?.let { range.third = frontend.expressionHandler.handle(it) } + } + } } - private fun handleStarExpr(starExpr: GoStandardLibrary.Ast.StarExpr): UnaryOperator { - val op = newUnaryOperator("*", postfix = false, prefix = false, rawNode = starExpr) - op.input = handle(starExpr.x) - - return op - } + private fun handleStarExpr(starExpr: GoStandardLibrary.Ast.StarExpr) = + newUnaryOperator("*", postfix = false, prefix = false, rawNode = starExpr).withChildren { + it.input = handle(starExpr.x) + } private fun handleTypeAssertExpr( typeAssertExpr: GoStandardLibrary.Ast.TypeAssertExpr @@ -398,39 +392,22 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : // "special" type assertion `.(type)`, which is used in a type switch to retrieve the type // of the variable. In this case we treat it as a special unary operator. return if (typeAssertExpr.type == null) { - val op = - newUnaryOperator( - ".(type)", - postfix = true, - prefix = false, - rawNode = typeAssertExpr - ) - op.input = handle(typeAssertExpr.x) - op + newUnaryOperator(".(type)", postfix = true, prefix = false, rawNode = typeAssertExpr) + .withChildren { it.input = handle(typeAssertExpr.x) } } else { - val cast = newCastExpression(rawNode = typeAssertExpr) - - // Parse the inner expression - cast.expression = handle(typeAssertExpr.x) + newCastExpression(rawNode = typeAssertExpr).withChildren { cast -> + // Parse the inner expression + cast.expression = handle(typeAssertExpr.x) - // The type can be null, but only in certain circumstances, i.e, a type switch - typeAssertExpr.type?.let { cast.castType = frontend.typeOf(it) } - cast + // The type can be null, but only in certain circumstances, i.e, a type switch + typeAssertExpr.type?.let { cast.castType = frontend.typeOf(it) } + } } } - private fun handleUnaryExpr(unaryExpr: GoStandardLibrary.Ast.UnaryExpr): UnaryOperator { - val op = - newUnaryOperator( - unaryExpr.opString, - postfix = false, - prefix = false, - rawNode = unaryExpr - ) - op.input = handle(unaryExpr.x) - - return op - } + private fun handleUnaryExpr(unaryExpr: GoStandardLibrary.Ast.UnaryExpr) = + newUnaryOperator(unaryExpr.opString, postfix = false, prefix = false, rawNode = unaryExpr) + .withChildren { op -> op.input = handle(unaryExpr.x) } /** * handleCompositeLit handles a composite literal, which we need to translate into a combination @@ -443,32 +420,29 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : // the "outer" one. See below val type = compositeLit.type?.let { frontend.typeOf(it) } ?: unknownType() - val list = newInitializerListExpression(type, rawNode = compositeLit) - list.type = type + return newInitializerListExpression(type, rawNode = compositeLit).withChildren { list -> + list.type = type - val expressions = mutableListOf() - for (elem in compositeLit.elts) { - val expression = handle(elem) - expressions += expression - } - - list.initializers = expressions + val expressions = mutableListOf() + for (elem in compositeLit.elts) { + val expression = handle(elem) + expressions += expression + } - return list + list.initializers = expressions + } } - /* - // handleFuncLit handles a function literal, which we need to translate into a combination of a - // LambdaExpression and a function declaration. - */ - fun handleFuncLit(funcLit: GoStandardLibrary.Ast.FuncLit): LambdaExpression { - val lambda = newLambdaExpression(rawNode = funcLit) - // Parse the expression as a function declaration with a little trick - lambda.function = - frontend.declarationHandler.handle(funcLit.toDecl()) as? FunctionDeclaration - - return lambda - } + /** + * handleFuncLit handles a function literal, which we need to translate into a combination of a + * LambdaExpression and a function declaration. + */ + fun handleFuncLit(funcLit: GoStandardLibrary.Ast.FuncLit) = + newLambdaExpression(rawNode = funcLit).withChildren { lambda -> + // Parse the expression as a function declaration with a little trick + lambda.function = + frontend.declarationHandler.handle(funcLit.toDecl()) as? FunctionDeclaration + } companion object { val builtins = diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt index 656894b58d..b9719cd3b6 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt @@ -59,6 +59,11 @@ abstract class GoHandler): List { + return list.mapNotNull(this::handle) + } + abstract fun handleNode(node: HandlerNode): ResultNode /** diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt index f973d14f70..cee7472a8e 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt @@ -166,54 +166,53 @@ class GoLanguageFrontend(language: Language, ctx: Translatio currentFile = f currentFileSet = fset - val tu = newTranslationUnitDeclaration(file.absolutePath, rawNode = f) - scopeManager.resetToGlobal(tu) - currentTU = tu - - for (spec in f.imports) { - val import = specificationHandler.handle(spec) - scopeManager.addDeclaration(import) - } - - val p = newNamespaceDeclaration(f.name.name) - scopeManager.enterScope(p) - - try { - // we need to construct the package "path" (e.g. "encoding/json") out of the - // module path as well as the current directory in relation to the topLevel - var packagePath = file.parentFile.relativeTo(topLevel) - - // If we are in a module, we need to prepend the module path to it. There is an - // exception if we are in the "std" module, which represents the standard library - val modulePath = currentModule?.module?.mod?.path - if (modulePath != null && modulePath != "std") { - packagePath = File(modulePath).resolve(packagePath) + return newTranslationUnitDeclaration(file.absolutePath, rawNode = f).withChildren( + isGlobalScope = true + ) { tu -> + currentTU = tu + + for (spec in f.imports) { + val import = specificationHandler.handle(spec) + scopeManager.addDeclaration(import) } - p.path = packagePath.path - } catch (ex: IllegalArgumentException) { - log.error( - "Could not relativize package path to top level. Cannot set package path.", - ex - ) - } - - for (decl in f.decls) { - // Retrieve all top level declarations. One "Decl" could potentially - // contain multiple CPG declarations. - val declaration = declarationHandler.handle(decl) - if (declaration is DeclarationSequence) { - declaration.declarations.forEach { scopeManager.addDeclaration(it) } - } else { - scopeManager.addDeclaration(declaration) - } - } - - scopeManager.leaveScope(p) + newNamespaceDeclaration(f.name.name) + .withChildren(hasScope = true) { p -> + try { + // we need to construct the package "path" (e.g. "encoding/json") out of the + // module path as well as the current directory in relation to the topLevel + var packagePath = file.parentFile.relativeTo(topLevel) + + // If we are in a module, we need to prepend the module path to it. There is + // an + // exception if we are in the "std" module, which represents the standard + // library + val modulePath = currentModule?.module?.mod?.path + if (modulePath != null && modulePath != "std") { + packagePath = File(modulePath).resolve(packagePath) + } - scopeManager.addDeclaration(p) + p.path = packagePath.path + } catch (ex: IllegalArgumentException) { + log.error( + "Could not relativize package path to top level. Cannot set package path.", + ex + ) + } - return tu + for (decl in f.decls) { + // Retrieve all top level declarations. One "Decl" could potentially + // contain multiple CPG declarations. + val declaration = declarationHandler.handle(decl) + if (declaration is DeclarationSequence) { + declaration.declarations.forEach { it.declare() } + } else { + declaration?.declare() + } + } + } + .declare() + } } override fun typeOf(type: GoStandardLibrary.Ast.Expr): Type { diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt index 5907dd03f3..1b0a45df15 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt @@ -28,6 +28,7 @@ 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.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util class SpecificationHandler(frontend: GoLanguageFrontend) : @@ -104,85 +105,73 @@ class SpecificationHandler(frontend: GoLanguageFrontend) : 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()) - } + ) = + newRecordDeclaration(name, "struct", rawNode = typeSpec).withChildren(hasScope = true) { + 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, rawNode = field) - frontend.scopeManager.addDeclaration(decl) + newFieldDeclaration(fieldName, type, modifiers, rawNode = field).declare() + } } } - 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, rawNode = field) - method.type = type - - frontend.scopeManager.enterScope(method) - - val params = (field.type as? GoStandardLibrary.Ast.FuncType)?.params - if (params != null) { - frontend.declarationHandler.handleFuncParams(params) + ) = + newRecordDeclaration(typeSpec.name.name, "interface", rawNode = typeSpec).withChildren( + hasScope = true + ) { record -> + // Make sure to register the type + frontend.typeManager.registerType(record.toType()) + + 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()) { + newMethodDeclaration(field.names[0].name, rawNode = field) + .withChildren(hasScope = true) { method -> + method.type = type + + val params = (field.type as? GoStandardLibrary.Ast.FuncType)?.params + if (params != null) { + frontend.declarationHandler.handleFuncParams(params) + } + } + .declare() + } 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(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 @@ -213,22 +202,22 @@ class SpecificationHandler(frontend: GoLanguageFrontend) : } 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 + tuple += + newVariableDeclaration(fqn, rawNode = valueSpec) + .withChildren { decl -> + 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]) + } + } + .declare() } return tuple } else { @@ -246,54 +235,59 @@ class SpecificationHandler(frontend: GoLanguageFrontend) : } 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" - ) + sequence += + newVariableDeclaration(fqn, rawNode = valueSpec).withChildren { decl -> + if (type != null) { + decl.type = type as Type } else { - decl.initializer = frontend.expressionHandler.handle(initializerExpr) + decl.type = autoType() } - } - type = frontend.declCtx.constType - if (type != null) { - decl.type = type - } - } + if (valueSpec.values.size > nameIdx) { + // the initializer is in the "Values" slice with the respective index + decl.initializer = + frontend.expressionHandler.handle(valueSpec.values[nameIdx]) + } - sequence += decl + // 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 as Type + } + } + } } return sequence diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt index dcc0154fac..198c192273 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt @@ -60,64 +60,61 @@ class StatementHandler(frontend: GoLanguageFrontend) : } } - private fun handleAssignStmt(assignStmt: GoStandardLibrary.Ast.AssignStmt): AssignExpression { - val lhs = assignStmt.lhs.map { frontend.expressionHandler.handle(it) } - val rhs = assignStmt.rhs.map { frontend.expressionHandler.handle(it) } - - // We need to explicitly set the operator code on this assignment as - // something which potentially declares a variable, so we can resolve this - // in our extra pass. - val operatorCode = - if (assignStmt.tok == 47) { - ":=" - } else { - "=" + /** + * Handles an assignment statement. We need to explicitly set the operator code on this + * assignment as something which potentially declares a variable, so we can resolve this in our + * extra pass. + */ + private fun handleAssignStmt(assignStmt: GoStandardLibrary.Ast.AssignStmt) = + newAssignExpression( + operatorCode = + if (assignStmt.tok == 47) { + ":=" + } else { + "=" + }, + rawNode = assignStmt + ) + .withChildren { expr -> + expr.rhs = frontend.expressionHandler.handle(assignStmt.rhs) + expr.lhs = frontend.expressionHandler.handle(assignStmt.lhs) } - return newAssignExpression(operatorCode, lhs, rhs, rawNode = assignStmt) - } - - private fun handleBranchStmt(branchStmt: GoStandardLibrary.Ast.BranchStmt): Statement { + private fun handleBranchStmt(branchStmt: GoStandardLibrary.Ast.BranchStmt) = when (branchStmt.tokString) { "break" -> { - val stmt = newBreakStatement(rawNode = branchStmt) - branchStmt.label?.let { stmt.label = it.name } - return stmt + newBreakStatement(rawNode = branchStmt).withChildren { stmt -> + branchStmt.label?.let { stmt.label = it.name } + } } "continue" -> { - val stmt = newContinueStatement(rawNode = branchStmt) - branchStmt.label?.let { stmt.label = it.name } - return stmt + newContinueStatement(rawNode = branchStmt).withChildren { stmt -> + branchStmt.label?.let { stmt.label = it.name } + } } "goto" -> { - val stmt = newGotoStatement(rawNode = branchStmt) - branchStmt.label?.let { stmt.labelName = it.name } - return stmt + newGotoStatement(rawNode = branchStmt).withChildren { stmt -> + branchStmt.label?.let { stmt.labelName = it.name } + } } + else -> + newProblemExpression( + "unknown token \"${branchStmt.tokString}\" in branch statement" + ) } - return newProblemExpression("unknown token \"${branchStmt.tokString}\" in branch statement") - } - - private fun handleBlockStmt(blockStmt: GoStandardLibrary.Ast.BlockStmt): Statement { - val compound = newBlock(rawNode = blockStmt) - - frontend.scopeManager.enterScope(compound) - - for (stmt in blockStmt.list) { - val node = handle(stmt) - // Do not add case statements to the block because the already add themselves in - // handleCaseClause. Otherwise, the order of case's would be wrong - if (node !is CaseStatement) { - compound += node + private fun handleBlockStmt(blockStmt: GoStandardLibrary.Ast.BlockStmt) = + newBlock(rawNode = blockStmt).withChildren(hasScope = true) { + for (stmt in blockStmt.list) { + val node = handle(stmt) + // Do not add case statements to the block because the already add themselves in + // handleCaseClause. Otherwise, the order of case's would be wrong + if (node !is CaseStatement) { + it += node + } } } - frontend.scopeManager.leaveScope(compound) - - return compound - } - private fun handleCaseClause( caseClause: GoStandardLibrary.Ast.CaseClause, typeSwitchLhs: Node? = null, @@ -168,30 +165,36 @@ class StatementHandler(frontend: GoLanguageFrontend) : val stmt = newDeclarationStatement() stmt.isImplicit = true - val decl = newVariableDeclaration(typeSwitchLhs.name) - if (case is CaseStatement) { - decl.type = (case.caseExpression as? TypeExpression)?.type ?: unknownType() - } else { - // We need to work with type listeners here because they might not have their type - // yet - typeSwitchRhs.registerTypeObserver( - object : HasType.TypeObserver { - override fun typeChanged(newType: Type, src: HasType) { - decl.type = newType - } - - override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { - // Nothing to do - } + val decl = + newVariableDeclaration(typeSwitchLhs.name).withChildren { + if (case is CaseStatement) { + it.type = (case.caseExpression as? TypeExpression)?.type ?: unknownType() + } else { + // We need to work with type listeners here because they might not have + // their type + // yet + typeSwitchRhs.registerTypeObserver( + object : HasType.TypeObserver { + override fun typeChanged(newType: Type, src: HasType) { + it.type = newType + } + + override fun assignedTypeChanged( + assignedTypes: Set, + src: HasType + ) { + // Nothing to do + } + } + ) } - ) - } - decl.initializer = typeSwitchRhs + it.initializer = typeSwitchRhs + } // Add the variable to the declaration statement as well as to the current scope (aka // our block wrapper) stmt.addToPropertyEdgeDeclaration(decl) - frontend.scopeManager.addDeclaration(decl) + decl.declare() if (block != null) { block += stmt @@ -220,19 +223,18 @@ class StatementHandler(frontend: GoLanguageFrontend) : private fun handleDeclStmt(declStmt: GoStandardLibrary.Ast.DeclStmt): DeclarationStatement { // Let's create a variable declaration (wrapped with a declaration stmt) with // this, because we define the variable here - val stmt = newDeclarationStatement(rawNode = declStmt) - val sequence = frontend.declarationHandler.handle(declStmt.decl) - if (sequence is DeclarationSequence) { - for (declaration in sequence.declarations) { - frontend.scopeManager.addDeclaration(declaration) + return newDeclarationStatement(rawNode = declStmt).withChildren { stmt -> + val sequence = frontend.declarationHandler.handle(declStmt.decl) + if (sequence is DeclarationSequence) { + for (declaration in sequence.declarations) { + frontend.scopeManager.addDeclaration(declaration) + } + stmt.declarations = sequence.asList() + } else { + frontend.scopeManager.addDeclaration(sequence) + stmt.singleDeclaration = sequence } - stmt.declarations = sequence.asList() - } else { - frontend.scopeManager.addDeclaration(sequence) - stmt.singleDeclaration = sequence } - - return stmt } /** @@ -241,11 +243,9 @@ class StatementHandler(frontend: GoLanguageFrontend) : * this 1:1, so we basically we create a call expression to a built-in call. // We adjust the * EOG of the call later in an extra pass. */ - private fun handleDeferStmt(deferStmt: GoStandardLibrary.Ast.DeferStmt): UnaryOperator { - val op = newUnaryOperator("defer", postfix = false, prefix = true, rawNode = deferStmt) - op.input = frontend.expressionHandler.handle(deferStmt.call) - return op - } + private fun handleDeferStmt(deferStmt: GoStandardLibrary.Ast.DeferStmt) = + newUnaryOperator("defer", postfix = false, prefix = true, rawNode = deferStmt) + .withChildren { op -> op.input = frontend.expressionHandler.handle(deferStmt.call) } /** * This function handles the `go` statement, which is a special keyword in go that starts the @@ -253,182 +253,120 @@ class StatementHandler(frontend: GoLanguageFrontend) : * we create a call expression to a built-in call. */ private fun handleGoStmt(goStmt: GoStandardLibrary.Ast.GoStmt): CallExpression { + // TODO: this will not set the ast parent of the callee correctly val ref = newReference("go") - val call = newCallExpression(ref, "go", rawNode = goStmt) - call += frontend.expressionHandler.handle(goStmt.call) - - return call - } - - private fun handleForStmt(forStmt: GoStandardLibrary.Ast.ForStmt): ForStatement { - val stmt = newForStatement(rawNode = forStmt) - - frontend.scopeManager.enterScope(stmt) - - forStmt.init?.let { stmt.initializerStatement = handle(it) } - forStmt.cond?.let { stmt.condition = frontend.expressionHandler.handle(it) } - forStmt.post?.let { stmt.iterationStatement = handle(it) } - forStmt.body?.let { stmt.statement = handle(it) } - - frontend.scopeManager.leaveScope(stmt) - - return stmt - } - - private fun handleIncDecStmt(incDecStmt: GoStandardLibrary.Ast.IncDecStmt): UnaryOperator { - val op = - newUnaryOperator( - incDecStmt.tokString, - postfix = true, - prefix = false, - rawNode = incDecStmt - ) - op.input = frontend.expressionHandler.handle(incDecStmt.x) - - return op - } - - private fun handleIfStmt(ifStmt: GoStandardLibrary.Ast.IfStmt): IfStatement { - val stmt = newIfStatement(rawNode = ifStmt) - - frontend.scopeManager.enterScope(stmt) - - ifStmt.init?.let { stmt.initializerStatement = frontend.statementHandler.handle(it) } - - stmt.condition = frontend.expressionHandler.handle(ifStmt.cond) - stmt.thenStatement = frontend.statementHandler.handle(ifStmt.body) - - ifStmt.`else`?.let { stmt.elseStatement = frontend.statementHandler.handle(it) } - - frontend.scopeManager.leaveScope(stmt) - - return stmt - } - - private fun handleLabeledStmt(labeledStmt: GoStandardLibrary.Ast.LabeledStmt): LabelStatement { - val stmt = newLabelStatement(rawNode = labeledStmt) - stmt.subStatement = handle(labeledStmt.stmt) - stmt.label = labeledStmt.label.name - - return stmt - } - - private fun handleRangeStmt(rangeStmt: GoStandardLibrary.Ast.RangeStmt): ForEachStatement { - val forEach = newForEachStatement(rawNode = rangeStmt) - - frontend.scopeManager.enterScope(forEach) - - // TODO: Support other use cases that do not use DEFINE - if (rangeStmt.tokString == ":=") { - val stmt = newDeclarationStatement() - - // TODO: not really the best way to deal with this - // TODO: key type is always int. we could set this - rangeStmt.key?.let { - val ref = frontend.expressionHandler.handle(it) - if (ref is Reference) { - val key = newVariableDeclaration(ref.name, rawNode = it) - frontend.scopeManager.addDeclaration(key) - stmt.addToPropertyEdgeDeclaration(key) - } - } - - // TODO: not really the best way to deal with this - rangeStmt.value?.let { - val ref = frontend.expressionHandler.handle(it) - if (ref is Reference) { - val key = newVariableDeclaration(ref.name, rawNode = it) - frontend.scopeManager.addDeclaration(key) - stmt.addToPropertyEdgeDeclaration(key) - } - } - - forEach.variable = stmt + return newCallExpression(ref, "go", rawNode = goStmt).withChildren { + it += frontend.expressionHandler.handle(goStmt.call) } - - forEach.iterable = frontend.expressionHandler.handle(rangeStmt.x) - forEach.statement = frontend.statementHandler.handle(rangeStmt.body) - - frontend.scopeManager.leaveScope(forEach) - - return forEach } - private fun handleReturnStmt(returnStmt: GoStandardLibrary.Ast.ReturnStmt): ReturnStatement { - val `return` = newReturnStatement(rawNode = returnStmt) - - val results = returnStmt.results - if (results.isNotEmpty()) { - val expr = frontend.expressionHandler.handle(results[0]) - - // TODO: parse more than one result expression - `return`.returnValue = expr - } else { - // TODO: connect result statement to result variables + private fun handleForStmt(forStmt: GoStandardLibrary.Ast.ForStmt) = + newForStatement(rawNode = forStmt).withChildren(hasScope = true) { stmt -> + forStmt.init?.let { stmt.initializerStatement = handle(it) } + forStmt.cond?.let { stmt.condition = frontend.expressionHandler.handle(it) } + forStmt.post?.let { stmt.iterationStatement = handle(it) } + forStmt.body?.let { stmt.statement = handle(it) } } - return `return` - } - - private fun handleSendStmt(sendStmt: GoStandardLibrary.Ast.SendStmt): BinaryOperator { - val op = newBinaryOperator("<-", rawNode = sendStmt) - op.lhs = frontend.expressionHandler.handle(sendStmt.chan) - op.rhs = frontend.expressionHandler.handle(sendStmt.value) - - return op - } - - private fun handleSwitchStmt(switchStmt: GoStandardLibrary.Ast.SwitchStmt): Statement { - val switch = newSwitchStatement(rawNode = switchStmt) - - frontend.scopeManager.enterScope(switch) + private fun handleIncDecStmt(incDecStmt: GoStandardLibrary.Ast.IncDecStmt) = + newUnaryOperator(incDecStmt.tokString, postfix = true, prefix = false, rawNode = incDecStmt) + .withChildren { op -> op.input = frontend.expressionHandler.handle(incDecStmt.x) } - switchStmt.init?.let { switch.initializerStatement = handle(it) } - switchStmt.tag?.let { switch.selector = frontend.expressionHandler.handle(it) } + private fun handleIfStmt(ifStmt: GoStandardLibrary.Ast.IfStmt) = + newIfStatement(rawNode = ifStmt).withChildren(hasScope = true) { stmt -> + ifStmt.init?.let { stmt.initializerStatement = frontend.statementHandler.handle(it) } - val block = - handle(switchStmt.body) as? Block ?: return newProblemExpression("missing switch body") + stmt.condition = frontend.expressionHandler.handle(ifStmt.cond) + stmt.thenStatement = frontend.statementHandler.handle(ifStmt.body) - switch.statement = block + ifStmt.`else`?.let { stmt.elseStatement = frontend.statementHandler.handle(it) } + } - frontend.scopeManager.leaveScope(switch) + private fun handleLabeledStmt(labeledStmt: GoStandardLibrary.Ast.LabeledStmt) = + newLabelStatement(rawNode = labeledStmt).withChildren { stmt -> + stmt.subStatement = handle(labeledStmt.stmt) + stmt.label = labeledStmt.label.name + } - return switch - } + private fun handleRangeStmt(rangeStmt: GoStandardLibrary.Ast.RangeStmt) = + newForEachStatement(rawNode = rangeStmt).withChildren(hasScope = true) { forEach -> + // TODO: Support other use cases that do not use DEFINE + if (rangeStmt.tokString == ":=") { + forEach.variable = + newDeclarationStatement().withChildren { stmt -> + // TODO: not really the best way to deal with this + // TODO: key type is always int. we could set this + rangeStmt.key?.let { + val ref = frontend.expressionHandler.handle(it) + if (ref is Reference) { + stmt += newVariableDeclaration(ref.name, rawNode = it).declare() + } + } - private fun handleTypeSwitchStmt( - typeSwitchStmt: GoStandardLibrary.Ast.TypeSwitchStmt - ): SwitchStatement { - val switch = newSwitchStatement(rawNode = typeSwitchStmt) + // TODO: not really the best way to deal with this + rangeStmt.value?.let { + val ref = frontend.expressionHandler.handle(it) + if (ref is Reference) { + stmt += newVariableDeclaration(ref.name, rawNode = it).declare() + } + } + } + } - frontend.scopeManager.enterScope(switch) + forEach.iterable = frontend.expressionHandler.handle(rangeStmt.x) + forEach.statement = frontend.statementHandler.handle(rangeStmt.body) + } - typeSwitchStmt.init?.let { switch.initializerStatement = handle(it) } + private fun handleReturnStmt(returnStmt: GoStandardLibrary.Ast.ReturnStmt) = + newReturnStatement(rawNode = returnStmt).withChildren { `return` -> + val results = returnStmt.results + if (results.isNotEmpty()) { + val expr = frontend.expressionHandler.handle(results[0]) - val assign = frontend.statementHandler.handle(typeSwitchStmt.assign) - val (lhs, rhs) = - if (assign is AssignExpression) { - val rhs = assign.rhs.singleOrNull() - switch.selector = rhs - Pair(assign.lhs.singleOrNull(), (rhs as? UnaryOperator)?.input) + // TODO: parse more than one result expression + `return`.returnValue = expr } else { - Pair(null, null) + // TODO: connect result statement to result variables } + } - val body = newBlock(rawNode = typeSwitchStmt.body) + private fun handleSendStmt(sendStmt: GoStandardLibrary.Ast.SendStmt) = + newBinaryOperator("<-", rawNode = sendStmt).withChildren { op -> + op.lhs = frontend.expressionHandler.handle(sendStmt.chan) + op.rhs = frontend.expressionHandler.handle(sendStmt.value) + } - frontend.scopeManager.enterScope(body) + private fun handleSwitchStmt(switchStmt: GoStandardLibrary.Ast.SwitchStmt) = + newSwitchStatement(rawNode = switchStmt).withChildren(hasScope = true) { switch -> + switchStmt.init?.let { switch.initializerStatement = handle(it) } + switchStmt.tag?.let { switch.selector = frontend.expressionHandler.handle(it) } - for (c in typeSwitchStmt.body.list.filterIsInstance()) { - handleCaseClause(c, lhs, rhs) + switch.statement = + handle(switchStmt.body) as? Block ?: newProblemExpression("missing switch body") } - frontend.scopeManager.leaveScope(body) - - switch.statement = body + private fun handleTypeSwitchStmt(typeSwitchStmt: GoStandardLibrary.Ast.TypeSwitchStmt) = + newSwitchStatement(rawNode = typeSwitchStmt).withChildren(hasScope = true) { switch -> + typeSwitchStmt.init?.let { switch.initializerStatement = handle(it) } - frontend.scopeManager.leaveScope(switch) + val assign = frontend.statementHandler.handle(typeSwitchStmt.assign) + val (lhs, rhs) = + if (assign is AssignExpression) { + val rhs = assign.rhs.singleOrNull() + switch.selector = rhs + Pair(assign.lhs.singleOrNull(), (rhs as? UnaryOperator)?.input) + } else { + Pair(null, null) + } - return switch - } + switch.statement = + newBlock(rawNode = typeSwitchStmt.body).withChildren(hasScope = true) { + for (c in + typeSwitchStmt.body.list.filterIsInstance< + GoStandardLibrary.Ast.CaseClause + >()) { + handleCaseClause(c, lhs, rhs) + } + } + } }