Skip to content

Commit

Permalink
Model values of Enum entries in Java with constructor call (#1552)
Browse files Browse the repository at this point in the history
* First attempt: Model with constructor call

* Add a comment for the future, be more reliant

* Add a test
  • Loading branch information
KuechA committed May 17, 2024
1 parent 48626db commit 5dd396f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionType.Companion.computeType
import de.fraunhofer.aisec.cpg.graph.types.ParameterizedType
import de.fraunhofer.aisec.cpg.graph.types.PointerType
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.matchesSignature
import java.util.function.Supplier
import kotlin.collections.set

Expand Down Expand Up @@ -206,12 +207,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) :

if (frontend.scopeManager.currentScope is RecordScope) {
// We need special handling if this is a so called "inner class". In this case we need
// to
// store
// to store
// a "this" reference to the outer class, so methods can use a "qualified this"
// (OuterClass.this.someFunction()). This is the same as the java compiler does. The
// reference
// is stored as an implicit field.
// reference is stored as an implicit field.
processInnerRecord(recordDeclaration)
}
return recordDeclaration
Expand Down Expand Up @@ -307,22 +306,20 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) :

frontend.scopeManager.enterScope(enumDeclaration)

processRecordMembers(enumDecl, enumDeclaration)

val entries = enumDecl.entries.mapNotNull { handle(it) as EnumConstantDeclaration? }
entries.forEach { it.type = this.objectType(enumDeclaration.name) }
enumDeclaration.entries = entries

processRecordMembers(enumDecl, enumDeclaration)

frontend.scopeManager.leaveScope(enumDeclaration)

if (frontend.scopeManager.currentScope is RecordScope) {
// We need special handling if this is a so called "inner class". In this case we need
// to
// store
// to store
// a "this" reference to the outer class, so methods can use a "qualified this"
// (OuterClass.this.someFunction()). This is the same as the java compiler does. The
// reference
// is stored as an implicit field.
// reference is stored as an implicit field.
processInnerRecord(enumDeclaration)
}
return enumDeclaration
Expand Down Expand Up @@ -405,7 +402,30 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) :
fun handleEnumConstantDeclaration(
enumConstDecl: com.github.javaparser.ast.body.EnumConstantDeclaration
): EnumConstantDeclaration {
return this.newEnumConstantDeclaration(enumConstDecl.nameAsString, rawNode = enumConstDecl)
val currentEnum = frontend.scopeManager.currentRecord
val result =
this.newEnumConstantDeclaration(enumConstDecl.nameAsString, rawNode = enumConstDecl)
if (enumConstDecl.arguments.isNotEmpty()) {
val arguments =
enumConstDecl.arguments.mapNotNull {
frontend.expressionHandler.handle(it)
as? de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
}
// TODO: This call resolution in the frontend might fail, in particular if we haven't
// processed the constructor yet. Should be cleaned up in the future but requires
// changes to the starting points of call/symbol resolution.
val matchingConstructor =
currentEnum?.constructors?.singleOrNull {
it.matchesSignature(arguments.map { it.type }).isDirectMatch
}

val constructExpr =
newConstructExpression(matchingConstructor?.name ?: currentEnum?.name)
arguments.forEach { constructExpr.addArgument(it) }
matchingConstructor?.let { constructExpr.constructor = matchingConstructor }
result.initializer = constructExpr
}
return result
}

fun /* TODO refine return type*/ handleAnnotationDeclaration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,23 @@ internal class JavaLanguageFrontendTest : BaseTest() {
assertNotNull(constructor.parameters["value"])
assertNotNull(constructor.bodyOrNull<AssignExpression>(0))

val entryOne = enum.entries.singleOrNull { it.name.localName == "VALUE_ONE" }
assertEquals(
1,
((entryOne?.initializer as? ConstructExpression)?.arguments?.singleOrNull()
as? Literal<*>)
?.value
)
assertEquals(constructor, (entryOne?.initializer as? ConstructExpression)?.constructor)
val entryTwo = enum.entries.singleOrNull { it.name.localName == "VALUE_TWO" }
assertEquals(
2,
((entryTwo?.initializer as? ConstructExpression)?.arguments?.singleOrNull()
as? Literal<*>)
?.value
)
assertEquals(constructor, (entryTwo?.initializer as? ConstructExpression)?.constructor)

val mainMethod = enum.methods["main"]
assertNotNull(mainMethod)
assertNotNull(mainMethod.parameters["args"])
Expand Down

0 comments on commit 5dd396f

Please sign in to comment.