diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/DeclarationHandler.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/DeclarationHandler.kt index 20edcaaeb7..182695dfd7 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/DeclarationHandler.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/DeclarationHandler.kt @@ -1,10 +1,41 @@ +/* + * 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.ruby import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration +import de.fraunhofer.aisec.cpg.graph.newFunctionDeclaration import de.fraunhofer.aisec.cpg.graph.newParameterDeclaration +import de.fraunhofer.aisec.cpg.graph.newReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import org.jruby.ast.ArgumentNode +import org.jruby.ast.MethodDefNode import org.jruby.ast.Node class DeclarationHandler(lang: RubyLanguageFrontend) : @@ -12,6 +43,7 @@ class DeclarationHandler(lang: RubyLanguageFrontend) : init { map.put(ArgumentNode::class.java, ::handleArgumentNode) + map.put(MethodDefNode::class.java, ::handleMethodDefNode) } private fun handleArgumentNode(node: Node?): Declaration? { @@ -19,9 +51,41 @@ class DeclarationHandler(lang: RubyLanguageFrontend) : return null } - return newParameterDeclaration( - node.name.idString(), - variadic = false - ) + return newParameterDeclaration(node.name.idString(), variadic = false) + } + + private fun handleMethodDefNode(node: Node): FunctionDeclaration? { + if (node !is MethodDefNode) { + return null + } + + val func = newFunctionDeclaration(node.name.idString()) + + frontend.scopeManager.enterScope(func) + + for (arg in node.argsNode.args) { + val param = this.handle(arg) + param?.let { frontend.scopeManager.addDeclaration(it) } + } + + val body = frontend.statementHandler.handle(node.bodyNode) + if (body is Block) { + // get the last statement + val lastStatement = body.statements.lastOrNull() + + // add an implicit return statement, if there is no return statement + if (lastStatement !is ReturnStatement) { + val returnStatement = newReturnStatement("return") + returnStatement.isImplicit = true + body += returnStatement + + // TODO: Ruby returns the last expression, if there is no explicit return + } + } + func.body = body + + frontend.scopeManager.leaveScope(func) + + return func } } diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/ExpressionHandler.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/ExpressionHandler.kt index d5431c0bec..7d6b25e837 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/ExpressionHandler.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/ExpressionHandler.kt @@ -1,7 +1,33 @@ +/* + * 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.ruby import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression @@ -18,8 +44,10 @@ class ExpressionHandler(lang: RubyLanguageFrontend) : map.put(IterNode::class.java, ::handleIterNode) map.put(StrNode::class.java, ::handleStrNode) map.put(DVarNode::class.java, ::handleDVarNode) + map.put(LocalVarNode::class.java, ::handleLocalVarNode) map.put(AttrAssignNode::class.java, ::handleAttrAssignNode) map.put(AssignableNode::class.java, ::handleAssignableNode) + map.put(ReturnNode::class.java, ::handleReturnNode) } private fun handleFCallNode(node: Node?): Statement? { @@ -59,9 +87,15 @@ class ExpressionHandler(lang: RubyLanguageFrontend) : return null } - return newReference( - node.name.idString() - ) + return newReference(node.name.idString()) + } + + private fun handleLocalVarNode(node: Node?): Statement? { + if (node !is LocalVarNode) { + return null + } + + return newReference(node.name.idString()) } private fun handleAssignableNode(node: Node?): Statement? { @@ -77,10 +111,7 @@ class ExpressionHandler(lang: RubyLanguageFrontend) : } // either a binary operator or a variable declaration - val lhs = - newReference( - name.idString() - ) + val lhs = newReference(name.idString()) val rhs = this.handle((node as AssignableNode).valueNode) as? Expression // can we resolve it? @@ -166,9 +197,17 @@ class ExpressionHandler(lang: RubyLanguageFrontend) : return null } - return newLiteral( - String(node.value.bytes()), - primitiveType("string") - ) + return newLiteral(String(node.value.bytes()), primitiveType("String")) + } + + private fun handleReturnNode(node: Node): ReturnStatement? { + if (node !is ReturnNode) { + return null + } + + val stmt = newReturnStatement() + (this.handle(node.valueNode) as? Expression)?.let { stmt.returnValue = it } + + return stmt } } diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt index e7f01ad06e..93969276d9 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt @@ -1,8 +1,32 @@ +/* + * 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.ruby import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.* -import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.types.* diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontend.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontend.kt index 9cbeb2dfc9..e3f95a9d30 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontend.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontend.kt @@ -1,27 +1,47 @@ +/* + * 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.ruby import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newFunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.newTranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import org.checkerframework.checker.nullness.qual.NonNull import org.jruby.Ruby import org.jruby.ast.BlockNode +import org.jruby.ast.MethodDefNode import org.jruby.ast.RootNode import org.jruby.parser.Parser import org.jruby.parser.ParserConfiguration -class RubyLanguageFrontend( - language: RubyLanguage, - ctx: @NonNull TranslationContext -) : LanguageFrontend(language, ctx) { +class RubyLanguageFrontend(language: RubyLanguage, ctx: @NonNull TranslationContext) : + LanguageFrontend(language, ctx) { val declarationHandler: DeclarationHandler = DeclarationHandler(this) val expressionHandler: ExpressionHandler = ExpressionHandler(this) val statementHandler: StatementHandler = StatementHandler(this) @@ -36,8 +56,7 @@ class RubyLanguageFrontend( file.inputStream(), null, ParserConfiguration(ruby, 0, false, true, false) - ) as - RootNode + ) as RootNode return handleRootNode(node, file) } @@ -47,18 +66,20 @@ class RubyLanguageFrontend( scopeManager.resetToGlobal(tu) - // wrap everything into a virtual global function because we only have declarations on the - // top - val func = - newFunctionDeclaration(file.nameWithoutExtension + "_global", codeOf(node)) - - scopeManager.enterScope(func) - - func.body = statementHandler.handle(node.bodyNode as BlockNode) - - scopeManager.leaveScope(func) - - scopeManager.addDeclaration(func) + // The root node can either contain a single node or a block node + if (node.bodyNode is MethodDefNode) { + val decl = declarationHandler.handle(node.bodyNode) + scopeManager.addDeclaration(decl) + } else if (node.bodyNode is BlockNode) { + // Otherwise, we need to loop over the block + val block = node.bodyNode as BlockNode + for (node in block) { + if (node is MethodDefNode) { + val decl = declarationHandler.handle(node) + scopeManager.addDeclaration(decl) + } + } + } return tu } @@ -72,7 +93,7 @@ class RubyLanguageFrontend( } override fun typeOf(type: org.jruby.ast.Node): Type { - return unknownType() + return autoType() } override fun setComment(s: Node, ctx: org.jruby.ast.Node) {} diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/StatementHandler.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/StatementHandler.kt index 4cfe99106b..ba9dd72681 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/StatementHandler.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/StatementHandler.kt @@ -1,9 +1,32 @@ +/* + * 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.ruby import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.graph.newBlock -import de.fraunhofer.aisec.cpg.graph.newReturnStatement -import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression @@ -24,26 +47,11 @@ class StatementHandler(lang: RubyLanguageFrontend) : blockNode.containsVariableAssignment() val compoundStatement = newBlock() - for (node in blockNode) { + for (node in blockNode.filterNotNull()) { val statement = frontend.expressionHandler.handle(node) statement?.let { compoundStatement.addStatement(it) } } - val statements = compoundStatement.statements - - // get the last statement - var lastStatement: Statement? = null - if (statements.isNotEmpty()) { - lastStatement = statements[statements.size - 1] - } - - // add an implicit return statement, if there is none - if (lastStatement !is ReturnStatement) { - val returnStatement = newReturnStatement("return") - returnStatement.isImplicit = true - compoundStatement.addStatement(returnStatement) - } - return compoundStatement } } diff --git a/cpg-language-ruby/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontendTest.kt b/cpg-language-ruby/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontendTest.kt new file mode 100644 index 0000000000..2f1360990d --- /dev/null +++ b/cpg-language-ruby/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguageFrontendTest.kt @@ -0,0 +1,54 @@ +/* + * 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.ruby + +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.graph.* +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertNotNull + +class RubyLanguageFrontendTest { + @Test + fun testFunctionDeclaration() { + val topLevel = Path.of("src", "test", "resources", "ruby") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("function.rb").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + val myFunction = tu.functions["my_function"] + assertNotNull(myFunction) + + val anotherFunction = tu.functions["another_function"] + assertNotNull(anotherFunction) + } +} diff --git a/cpg-language-ruby/src/test/resources/ruby/function.rb b/cpg-language-ruby/src/test/resources/ruby/function.rb new file mode 100644 index 0000000000..fc27bc5c9d --- /dev/null +++ b/cpg-language-ruby/src/test/resources/ruby/function.rb @@ -0,0 +1,8 @@ +def my_function(value) + out = 2 * value + return out +end + +def another_function() + a = 2 +end \ No newline at end of file