Skip to content

Commit

Permalink
Added very simple unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Oct 18, 2023
1 parent 7b1b0c2 commit 6909a0f
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,91 @@
/*
* 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) :
Handler<Declaration, Node, RubyLanguageFrontend>({ ProblemDeclaration() }, lang) {

init {
map.put(ArgumentNode::class.java, ::handleArgumentNode)
map.put(MethodDefNode::class.java, ::handleMethodDefNode)
}

private fun handleArgumentNode(node: Node?): Declaration? {
if (node !is ArgumentNode) {
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
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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? {
Expand Down Expand Up @@ -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? {
Expand All @@ -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?
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down
Original file line number Diff line number Diff line change
@@ -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<org.jruby.ast.Node, org.jruby.ast.Node>(language, ctx) {
class RubyLanguageFrontend(language: RubyLanguage, ctx: @NonNull TranslationContext) :
LanguageFrontend<org.jruby.ast.Node, org.jruby.ast.Node>(language, ctx) {
val declarationHandler: DeclarationHandler = DeclarationHandler(this)
val expressionHandler: ExpressionHandler = ExpressionHandler(this)
val statementHandler: StatementHandler = StatementHandler(this)
Expand All @@ -36,8 +56,7 @@ class RubyLanguageFrontend(
file.inputStream(),
null,
ParserConfiguration(ruby, 0, false, true, false)
) as
RootNode
) as RootNode

return handleRootNode(node, file)
}
Expand All @@ -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
}
Expand All @@ -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) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
}
Loading

0 comments on commit 6909a0f

Please sign in to comment.