Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add members, methods and imports to EnumDeclarations #1450

Merged
merged 7 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.graph.unknownType
import de.fraunhofer.aisec.cpg.isDerivedFrom
import java.io.File
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) {
}
val base = importables[matcher.group("base")]
var members = setOf<ValueDeclaration>()
if (base is EnumDeclaration) {
members = getOrCreateMembers(base, matcher.group("member"))
} else if (base is RecordDeclaration) {
if (base is RecordDeclaration) {
members = getOrCreateMembers(base, matcher.group("member"))
}
staticImports.addAll(members)
Expand Down Expand Up @@ -104,10 +102,6 @@ open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) {
return targetTypes.mapNotNull { importables[it] }.toMutableSet()
}

protected fun getOrCreateMembers(base: EnumDeclaration, name: String): Set<ValueDeclaration> {
return base.entries.filter { it.name.localName == name }.toSet()
}

protected fun getOrCreateMembers(base: RecordDeclaration, name: String): Set<ValueDeclaration> {
val memberMethods = base.methods.filter { it.name.localName.endsWith(name) }.toMutableSet()

Expand All @@ -123,12 +117,18 @@ open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) {
base.superTypeDeclarations.flatMap { it.fields }.filter { it.name.localName == name }
)

val memberEntries = mutableSetOf<EnumConstantDeclaration>()
if (base is EnumDeclaration) {
base.entries[name]?.let { memberEntries.add(it) }
}

// now it gets weird: you can import a field and a number of methods that have the same
// name, all with a *single* static import...
// TODO(oxisto): Move all of the following code to the [Inference] class
val result = mutableSetOf<ValueDeclaration>()
result.addAll(memberMethods)
result.addAll(memberFields)
result.addAll(memberEntries)
if (result.isEmpty()) {
// the target might be a field or a method, we don't know. Thus, we need to create both
val targetField =
Expand Down Expand Up @@ -160,9 +160,7 @@ open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) {
Strategy::AST_FORWARD,
object : IVisitor<Node>() {
override fun visit(t: Node) {
if (t is EnumDeclaration) {
importables.putIfAbsent(t.name.toString(), t)
} else if (t is RecordDeclaration) {
if (t is RecordDeclaration) {
records.add(t)
importables.putIfAbsent(t.name.toString(), t)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
package de.fraunhofer.aisec.cpg.passes

import de.fraunhofer.aisec.cpg.InferenceConfiguration
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.frontends.*
import de.fraunhofer.aisec.cpg.graph.*
Expand Down Expand Up @@ -386,7 +387,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
// this in the call resolver instead
return null
}
var member: FieldDeclaration? = null
var member: ValueDeclaration? = null
val record = containingClass.recordDeclaration
if (record != null) {
member =
Expand All @@ -403,6 +404,9 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
.map { it.definition }
.firstOrNull()
}
if (member == null && record is EnumDeclaration) {
member = record.entries[reference.name.localName]
}
return member ?: handleUnknownField(containingClass, reference)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,112 +198,42 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) :
classInterDecl.typeParameters.map { ParameterizedType(it.nameAsString, language) }
)

// TODO: I cannot replicate the old partionedBy logic
val staticImports =
frontend.context
?.imports
?.filter { it.isStatic }
?.map {
var iName: String = it.nameAsString
// we need to ensure that x.* imports really preserve the asterisk!
if (it.isAsterisk && !iName.endsWith(".*")) {
iName += ".*"
}
iName
}
val imports =
frontend.context
?.imports
?.filter { !it.isStatic }
?.map {
var iName: String = it.nameAsString
// we need to ensure that x.* imports really preserve the asterisk!
if (it.isAsterisk && !iName.endsWith(".*")) {
iName += ".*"
}
iName
}
processImportDeclarations(recordDeclaration)

recordDeclaration.staticImportStatements = staticImports ?: listOf()
recordDeclaration.importStatements = imports ?: listOf()
frontend.scopeManager.enterScope(recordDeclaration)

// TODO: 'this' identifier for multiple instances?
for (decl in classInterDecl.members) {
(decl as? com.github.javaparser.ast.body.FieldDeclaration)?.let {
handle(it) // will be added via the scopemanager
}
?: when (decl) {
is MethodDeclaration -> {
val md =
handle(decl)
as de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration?
frontend.scopeManager.addDeclaration(md)
}
is ConstructorDeclaration -> {
val c =
handle(decl)
as
de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration?
frontend.scopeManager.addDeclaration(c)
}
is ClassOrInterfaceDeclaration -> {
frontend.scopeManager.addDeclaration(handle(decl))
}
is InitializerDeclaration -> {
val initializerBlock =
frontend.statementHandler.handleBlockStatement(decl.body)
initializerBlock.isStaticBlock = decl.isStatic
recordDeclaration.addStatement(initializerBlock)
}
else -> {
log.debug(
"Member {} of type {} is something that we do not parse yet: {}",
decl,
recordDeclaration.name,
decl.javaClass.simpleName
)
}
}
}
if (recordDeclaration.constructors.isEmpty()) {
val constructorDeclaration =
this.newConstructorDeclaration(
recordDeclaration.name.localName,
recordDeclaration,
)
.implicit(recordDeclaration.name.localName)
recordDeclaration.addConstructor(constructorDeclaration)
frontend.scopeManager.addDeclaration(constructorDeclaration)
}
frontend.processAnnotations(recordDeclaration, classInterDecl)
processRecordMembers(classInterDecl, recordDeclaration)
frontend.scopeManager.leaveScope(recordDeclaration)

// We need special handling if this is a so called "inner class". In this case we need 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.
if (frontend.scopeManager.currentScope is RecordScope) {
// Get all the information of the outer class (its name and the respective type). We
// need this to generate the field.
val scope = frontend.scopeManager.currentScope as RecordScope?
if (scope?.name != null) {
val fieldType = scope.name?.let { this.objectType(it) } ?: unknownType()

// Enter the scope of the inner class because the new field belongs there.
frontend.scopeManager.enterScope(recordDeclaration)
val field =
this.newFieldDeclaration("this$" + scope.name?.localName, fieldType, listOf())
.implicit("this$" + scope.name?.localName)
frontend.scopeManager.addDeclaration(field)
frontend.scopeManager.leaveScope(recordDeclaration)
}
// We need special handling if this is a so called "inner class". In this case we need
// 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.
processInnerRecord(recordDeclaration)
}
return recordDeclaration
}

private fun processInnerRecord(recordDeclaration: RecordDeclaration) {
// Get all the information of the outer class (its name and the respective type). We
// need this to generate the field.
val scope = frontend.scopeManager.currentScope as RecordScope?
if (scope?.name != null) {
val fieldType = scope.name?.let { this.objectType(it) } ?: unknownType()

// Enter the scope of the inner class because the new field belongs there.
frontend.scopeManager.enterScope(recordDeclaration)
val field =
this.newFieldDeclaration("this$" + scope.name?.localName, fieldType, listOf())
.implicit("this$" + scope.name?.localName)
frontend.scopeManager.addDeclaration(field)
frontend.scopeManager.leaveScope(recordDeclaration)
}
}

fun handleFieldDeclaration(
fieldDecl: com.github.javaparser.ast.body.FieldDeclaration
): FieldDeclaration {
Expand Down Expand Up @@ -360,15 +290,108 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) :
): EnumDeclaration {
val name = enumDecl.nameAsString
val enumDeclaration = this.newEnumDeclaration(name, rawNode = enumDecl)
val entries = enumDecl.entries.mapNotNull { handle(it) as EnumConstantDeclaration? }

entries.forEach { it.type = this.objectType(enumDeclaration.name) }
enumDeclaration.entries = entries
val superTypes = enumDecl.implementedTypes.map { frontend.getTypeAsGoodAsPossible(it) }
enumDeclaration.superClasses.addAll(superTypes)

processImportDeclarations(enumDeclaration)

frontend.scopeManager.enterScope(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
// 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.
processInnerRecord(enumDeclaration)
}
return enumDeclaration
}

private fun <T : TypeDeclaration<T>> processRecordMembers(
typeDecl: T,
recordDeclaration: RecordDeclaration,
) {
// TODO: 'this' identifier for multiple instances?
for (decl in typeDecl.members) {
(decl as? com.github.javaparser.ast.body.FieldDeclaration)?.let {
handle(it) // will be added via the scopemanager
}
?: when (decl) {
is MethodDeclaration -> {
val md =
handle(decl)
as de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration?
frontend.scopeManager.addDeclaration(md)
}
is ConstructorDeclaration -> {
val c =
handle(decl)
as
de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration?
frontend.scopeManager.addDeclaration(c)
}
is ClassOrInterfaceDeclaration -> {
frontend.scopeManager.addDeclaration(handle(decl))
}
is InitializerDeclaration -> {
val initializerBlock =
frontend.statementHandler.handleBlockStatement(decl.body)
initializerBlock.isStaticBlock = decl.isStatic
recordDeclaration.addStatement(initializerBlock)
}
else -> {
log.debug(
"Member {} of type {} is something that we do not parse yet: {}",
decl,
recordDeclaration.name,
decl.javaClass.simpleName
)
}
}
}
if (recordDeclaration.constructors.isEmpty()) {
val constructorDeclaration =
this.newConstructorDeclaration(
recordDeclaration.name.localName,
recordDeclaration,
)
.implicit(recordDeclaration.name.localName)
recordDeclaration.addConstructor(constructorDeclaration)
frontend.scopeManager.addDeclaration(constructorDeclaration)
}
frontend.processAnnotations(recordDeclaration, typeDecl)
}

private fun processImportDeclarations(recordDeclaration: RecordDeclaration) {
val allImports =
frontend.context
?.imports
?.map {
var iName: String = it.nameAsString
// we need to ensure that x.* imports really preserve the asterisk!
if (it.isAsterisk && !iName.endsWith(".*")) {
iName += ".*"
}
Pair(it, iName)
}
?.groupBy({ it.first.isStatic }, { it.second })

recordDeclaration.staticImportStatements = allImports?.get(true) ?: listOf()
recordDeclaration.importStatements = allImports?.get(false) ?: listOf()
}

/* Not so sure about the place of Annotations in the CPG currently */
fun handleEnumConstantDeclaration(
enumConstDecl: com.github.javaparser.ast.body.EnumConstantDeclaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,41 +29,21 @@ import com.github.javaparser.Range
import com.github.javaparser.TokenRange
import com.github.javaparser.ast.Node
import com.github.javaparser.ast.body.VariableDeclarator
import com.github.javaparser.ast.expr.ArrayAccessExpr
import com.github.javaparser.ast.expr.ArrayCreationExpr
import com.github.javaparser.ast.expr.ArrayInitializerExpr
import com.github.javaparser.ast.expr.BinaryExpr
import com.github.javaparser.ast.expr.BooleanLiteralExpr
import com.github.javaparser.ast.expr.CharLiteralExpr
import com.github.javaparser.ast.expr.ClassExpr
import com.github.javaparser.ast.expr.DoubleLiteralExpr
import com.github.javaparser.ast.expr.EnclosedExpr
import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.InstanceOfExpr
import com.github.javaparser.ast.expr.IntegerLiteralExpr
import com.github.javaparser.ast.expr.LiteralExpr
import com.github.javaparser.ast.expr.LongLiteralExpr
import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
import com.github.javaparser.ast.expr.ObjectCreationExpr
import com.github.javaparser.ast.expr.StringLiteralExpr
import com.github.javaparser.ast.expr.SuperExpr
import com.github.javaparser.ast.expr.ThisExpr
import com.github.javaparser.ast.expr.UnaryExpr
import com.github.javaparser.ast.expr.VariableDeclarationExpr
import com.github.javaparser.resolution.UnsolvedSymbolException
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration
import de.fraunhofer.aisec.cpg.frontends.Handler
import de.fraunhofer.aisec.cpg.frontends.HandlerInterface
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.statements.*
import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.Statement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConditionalExpression
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.graph.types.FunctionType
import de.fraunhofer.aisec.cpg.graph.types.Type
import java.util.function.Supplier
import kotlin.collections.set
import kotlin.jvm.optionals.getOrNull
Expand Down
Loading
Loading