Skip to content

Commit

Permalink
Go slice expression
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Mar 4, 2023
1 parent 017423a commit 3f761eb
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,24 @@ fun MetadataProvider.newArraySubscriptionExpression(
return node
}

/**
* Creates a new [SliceExpression]. The [MetadataProvider] receiver will be used to fill different
* meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires
* an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended
* argument.
*/
@JvmOverloads
fun MetadataProvider.newSliceExpression(
code: String? = null,
rawNode: Any? = null
): SliceExpression {
val node = SliceExpression()
node.applyMetadata(this, EMPTY_NAME, rawNode, code, true)

log(node)
return node
}

/**
* Creates a new [ArrayCreationExpression]. The [MetadataProvider] receiver will be used to fill
* different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase

/**
* The expression which represents the "subscription" or index on which the array is accessed.
* This can for example be a reference to another variable ([DeclaredReferenceExpression]) or a
* [Literal].
* This can for example be a reference to another variable ([DeclaredReferenceExpression]), a
* [Literal] or a [SliceExpression].
*/
@field:SubGraph("AST")
var subscriptExpression: Expression = ProblemExpression("could not parse index expression")
Expand All @@ -62,8 +62,18 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase
override val operatorCode: String
get() = "[]"

/**
* This helper function returns the subscript type of the [arrayType]. We have to differentiate
* here between to types of subscripts:
* * Slices (in the form of a [SliceExpression] return the same type as the array
* * Everything else (for example a [Literal] or any other [Expression] that is being evaluated)
* returns the de-referenced type
*/
private fun getSubscriptType(arrayType: Type): Type {
return arrayType.dereference()
return when (subscriptExpression) {
is SliceExpression -> arrayType
else -> arrayType.dereference()
}
}

override fun typeChanged(src: HasType, root: MutableList<HasType>, oldType: Type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.graph.statements.expressions

/**
* Represents the specification of a slice (e.g., of an array). Usually used in combination with an
* [ArraySubscriptionExpression] as the [ArraySubscriptionExpression.subscriptExpression].
*
* Examples can be found in Go:
* ```go
* a := []int{1,2,3}
* b := a[:1]
* ```
*
* or Python:
* ```python
* a = (1,2,3)
* b = a[:1]
* ```
*
* Individual meaning of the slice indices might differ per language.
*/
class SliceExpression : Expression() {

/** The lower bound of the slice. This index is usually *inclusive*. */
var lowerBound: Expression? = null

/** The upper bound of the slice. This index is usually *exclusive*. */
var upperBound: Expression? = null

/**
* Some languages offer a third value. The meaning depends completely on the language. For
* example, Python allows specifying a step, while Go allows to control the underlying array's
* capacity (not length).
*/
var third: Expression? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.TypeManager
import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
Expand Down Expand Up @@ -297,10 +294,18 @@ object TestUtils {

fun assertFullName(fqn: String, node: Node?, message: String? = null) {
assertNotNull(node)
asserter.assertEquals(message, fqn, node.name.toString())
assertEquals(fqn, node.name.toString(), message)
}

fun assertLocalName(localName: String, node: Node?, message: String? = null) {
assertNotNull(node)
asserter.assertEquals(message, localName, node.name.localName)
assertEquals(localName, node.name.localName, message)
}

/**
* Asserts that a) the expression in [expr] is a [Literal] and b) that it's value is equal to
* [expected].
*/
fun <T : Any?> assertLiteralValue(expected: T, expr: Expression?, message: String? = null) {
assertEquals(expected, assertIs<Literal<T>>(expr).value, message)
}
13 changes: 13 additions & 0 deletions cpg-language-go/src/main/golang/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type CastExpression Expression
type NewExpression Expression
type ArrayCreationExpression Expression
type ArraySubscriptionExpression Expression
type SliceExpression Expression
type ConstructExpression Expression
type InitializerListExpression Expression
type MemberCallExpression CallExpression
Expand Down Expand Up @@ -205,6 +206,18 @@ func (r *ArraySubscriptionExpression) SetSubscriptExpression(e *Expression) {
(*jnigi.ObjectRef)(r).CallMethod(env, "setSubscriptExpression", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass))
}

func (s *SliceExpression) SetLowerBound(e *Expression) {
(*jnigi.ObjectRef)(s).CallMethod(env, "setLowerBound", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass))
}

func (s *SliceExpression) SetUpperBound(e *Expression) {
(*jnigi.ObjectRef)(s).CallMethod(env, "setUpperBound", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass))
}

func (s *SliceExpression) SetThird(e *Expression) {
(*jnigi.ObjectRef)(s).CallMethod(env, "setThird", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass))
}

func (c *ConstructExpression) AddArgument(e *Expression) {
(*jnigi.ObjectRef)(c).CallMethod(env, "addArgument", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func (frontend *GoLanguageFrontend) NewArraySubscriptionExpression(fset *token.F
return (*cpg.ArraySubscriptionExpression)(frontend.NewExpression("ArraySubscriptionExpression", fset, astNode))
}

func (frontend *GoLanguageFrontend) NewSliceExpression(fset *token.FileSet, astNode ast.Node) *cpg.SliceExpression {
return (*cpg.SliceExpression)(frontend.NewExpression("SliceExpression", fset, astNode))
}

func (frontend *GoLanguageFrontend) NewConstructExpression(fset *token.FileSet, astNode ast.Node) *cpg.ConstructExpression {
return (*cpg.ConstructExpression)(frontend.NewExpression("ConstructExpression", fset, astNode))
}
Expand Down Expand Up @@ -104,6 +108,10 @@ func (frontend *GoLanguageFrontend) NewLiteral(fset *token.FileSet, astNode ast.
value = value.Cast("java/lang/Object")
}

if typ == nil {
panic("typ is nil")
}

return (*cpg.Literal)(frontend.NewExpression("Literal", fset, astNode, value, typ.Cast(cpg.TypeClass)))
}

Expand Down
36 changes: 34 additions & 2 deletions cpg-language-go/src/main/golang/frontend/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,8 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) (
e = (*cpg.Expression)(this.handleStarExpr(fset, v))
case *ast.SelectorExpr:
e = (*cpg.Expression)(this.handleSelectorExpr(fset, v))
case *ast.SliceExpr:
e = (*cpg.Expression)(this.handleSliceExpr(fset, v))
case *ast.KeyValueExpr:
e = (*cpg.Expression)(this.handleKeyValueExpr(fset, v))
case *ast.BasicLit:
Expand Down Expand Up @@ -960,13 +962,40 @@ func (this *GoLanguageFrontend) handleCallExpr(fset *token.FileSet, callExpr *as
return (*cpg.Expression)(c)
}

func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.Expression {
func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.ArraySubscriptionExpression {
a := this.NewArraySubscriptionExpression(fset, indexExpr)

a.SetArrayExpression(this.handleExpr(fset, indexExpr.X))
a.SetSubscriptExpression(this.handleExpr(fset, indexExpr.Index))

return (*cpg.Expression)(a)
return a
}

// handleSliceExpr handles a [ast.SliceExpr], which is an extended version of
// [ast.IndexExpr]. We are modelling this as a combination of a
// [cpg.ArraySubscriptionExpression] that contains a [cpg.SliceExpression] as
// its subscriptExpression to share some code between this and an index
// expression.
func (this *GoLanguageFrontend) handleSliceExpr(fset *token.FileSet, sliceExpr *ast.SliceExpr) *cpg.ArraySubscriptionExpression {
a := this.NewArraySubscriptionExpression(fset, sliceExpr)

a.SetArrayExpression(this.handleExpr(fset, sliceExpr.X))

// Build the slice expression
s := this.NewSliceExpression(fset, sliceExpr)
if sliceExpr.Low != nil {
s.SetLowerBound(this.handleExpr(fset, sliceExpr.Low))
}
if sliceExpr.High != nil {
s.SetUpperBound(this.handleExpr(fset, sliceExpr.High))
}
if sliceExpr.Max != nil {
s.SetThird(this.handleExpr(fset, sliceExpr.Max))
}

a.SetSubscriptExpression((*cpg.Expression)(s))

return a
}

func (this *GoLanguageFrontend) handleNewExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression {
Expand Down Expand Up @@ -1164,8 +1193,11 @@ func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.Bas
value = cpg.NewDouble(f)
t = cpg.TypeParser_createFrom("float64", lang)
case token.IMAG:
// TODO
t = &cpg.UnknownType_getUnknown(lang).Type
case token.CHAR:
value = cpg.NewString(lit.Value)
t = cpg.TypeParser_createFrom("rune", lang)
break
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class GoTypeCastingPass : Pass() {
override fun accept(t: TranslationResult) {
scopeManager = t.scopeManager

var walker = SubgraphWalker.ScopedWalker(scopeManager)
val walker = SubgraphWalker.ScopedWalker(scopeManager)
walker.registerHandler { _, parent, node ->
// We are only interested in CallExpressions
if (node is CallExpression) {
Expand All @@ -78,7 +78,7 @@ class GoTypeCastingPass : Pass() {
val language = parent.language ?: GoLanguage()

// First, check if this is a built-in type
if (language.primitiveTypes.contains(callee.name.toString())) {
if (language.builtInTypes.contains(callee.name.toString())) {
replaceCallWithCast(callee.name.toString(), language, parent, call)
} else {
// If not, then this could still refer to an existing type. We need to make sure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,18 @@ package de.fraunhofer.aisec.cpg.frontends.golang

import de.fraunhofer.aisec.cpg.TestUtils
import de.fraunhofer.aisec.cpg.assertFullName
import de.fraunhofer.aisec.cpg.assertLiteralValue
import de.fraunhofer.aisec.cpg.assertLocalName
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.bodyOrNull
import de.fraunhofer.aisec.cpg.graph.byNameOrNull
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertSame
import kotlin.test.*

class ExpressionTest {
@Test
Expand Down Expand Up @@ -80,4 +78,66 @@ class ExpressionTest {
val ignored = main.variables("_")
ignored.forEach { assertIs<CastExpression>(it.initializer) }
}

@Test
fun testSliceExpression() {
val topLevel = Path.of("src", "test", "resources", "golang")
val tu =
TestUtils.analyzeAndGetFirstTU(
listOf(topLevel.resolve("slices.go").toFile()),
topLevel,
true
) {
it.registerLanguage<GoLanguage>()
}
assertNotNull(tu)

val a = tu.variables["a"]
assertNotNull(a)
assertLocalName("int[]", a.type)

val b = tu.variables["b"]
assertNotNull(b)
assertLocalName("int[]", b.type)

// TODO: actually this should be an assign, not the initializer
// [:1]
var slice =
assertIs<SliceExpression>(
assertIs<ArraySubscriptionExpression>(b.initializer).subscriptExpression
)
assertNull(slice.lowerBound)
assertLiteralValue(1, slice.upperBound)
assertNull(slice.third)

val c = tu.variables["c"]
assertNotNull(c)
assertLocalName("int[]", c.type)

// [1:]
slice = assertIs(assertIs<ArraySubscriptionExpression>(c.initializer).subscriptExpression)
assertLiteralValue(1, slice.lowerBound)
assertNull(slice.upperBound)
assertNull(slice.third)

val d = tu.variables["d"]
assertNotNull(d)
assertLocalName("int[]", d.type)

// [0:1]
slice = assertIs(assertIs<ArraySubscriptionExpression>(d.initializer).subscriptExpression)
assertLiteralValue(0, slice.lowerBound)
assertLiteralValue(1, slice.upperBound)
assertNull(slice.third)

val e = tu.variables["e"]
assertNotNull(e)
assertLocalName("int[]", e.type)

// [0:1:1]
slice = assertIs(assertIs<ArraySubscriptionExpression>(e.initializer).subscriptExpression)
assertLiteralValue(0, slice.lowerBound)
assertLiteralValue(1, slice.upperBound)
assertLiteralValue(1, slice.third)
}
}
17 changes: 17 additions & 0 deletions cpg-language-go/src/test/resources/golang/slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "fmt"

func main() {
a := []int{1,2,3}
// [1]
b := a[:1]
// [2, 3]
c := a[1:]
// [1]
d := a[0:1]
// [1]
e := a[0:1:1]

fmt.Printf("%v %v %v %v %v", a, b, c, d, e)
}
Loading

0 comments on commit 3f761eb

Please sign in to comment.