From 592b8cebbe381cae912c2c2a03bde18862917698 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 18 Dec 2024 09:49:06 +0100 Subject: [PATCH] Fixing Java field access --- .../aisec/cpg/passes/ImportResolver.kt | 63 ++-- .../aisec/cpg/passes/SymbolResolver.kt | 2 +- .../aisec/cpg/passes/TypeResolver.kt | 4 + .../de/fraunhofer/aisec/cpg/test/TestUtils.kt | 2 +- .../cpg/frontends/java/ExpressionHandler.kt | 311 +++--------------- .../frontends/java/JavaLanguageFrontend.kt | 8 + .../aisec/cpg/passes/JavaExtraPass.kt | 83 +++++ .../cpg/enhancements/calls/SuperCallTest.kt | 9 +- .../cpg/frontends/java/StaticImportsTest.kt | 16 +- 9 files changed, 195 insertions(+), 303 deletions(-) create mode 100644 cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt index 50530e3ecb..60c6eb4b7e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt @@ -265,38 +265,43 @@ class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { } private fun handleImportDeclaration(import: ImportDeclaration) { - // We always need to search at the global scope because we are "importing" something, so by - // definition, this is not in the scope of the current file. - val scope = scopeManager.globalScope ?: return - - // Let's do some importing. We need to import either a wildcard - if (import.wildcardImport) { - val list = - scopeManager.lookupSymbolByName( - import.import, - import.language, - import.location, - scope - ) - val symbol = list.singleOrNull() - if (symbol != null) { - // In this case, the symbol must point to a name scope - val symbolScope = scopeManager.lookupScope(symbol) - if (symbolScope is NameScope) { - import.importedSymbols = symbolScope.symbols - } - } - } else { - // or a symbol directly - val list = - scopeManager - .lookupSymbolByName(import.import, import.language, import.location, scope) - .toMutableList() - import.importedSymbols = mutableMapOf(import.symbol to list) - } + import.updateImportedSymbols() } override fun cleanup() { // Nothing to do } } + +/** + * This function updates the [ImportDeclaration.importedSymbols]. This is done once at the beginning + * by the [ImportResolver]. However, we need to update this list once we infer new symbols in + * namespaces that are imported at a later stage (e.g., in the [TypeResolver]), otherwise they won't + * be visible to the later passes. + */ +context(Pass<*>) +fun ImportDeclaration.updateImportedSymbols() { + // We always need to search at the global scope because we are "importing" something, so by + // definition, this is not in the scope of the current file. + val scope = scopeManager.globalScope ?: return + + // Let's do some importing. We need to import either a wildcard + if (this.wildcardImport) { + val list = scopeManager.lookupSymbolByName(this.import, this.language, this.location, scope) + val symbol = list.singleOrNull() + if (symbol != null) { + // In this case, the symbol must point to a name scope + val symbolScope = scopeManager.lookupScope(symbol) + if (symbolScope is NameScope) { + this.importedSymbols = symbolScope.symbols + } + } + } else { + // or a symbol directly + val list = + scopeManager + .lookupSymbolByName(this.import, this.language, this.location, scope) + .toMutableList() + this.importedSymbols = mutableMapOf(this.symbol to list) + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index d463d64090..c544e77f5a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -214,7 +214,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Preparation for a future without legacy call resolving. Taking the first candidate is not // ideal since we are running into an issue with function pointers here (see workaround // below). - var wouldResolveTo = ref.candidates.singleOrNull() + var wouldResolveTo = ref.candidates.firstOrNull() // For now, we need to ignore reference expressions that are directly embedded into call // expressions, because they are the "callee" property. In the future, we will use this diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index ce33e5205e..c14b04a806 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.ImportDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.DeclaresType import de.fraunhofer.aisec.cpg.graph.types.ObjectType @@ -141,6 +142,9 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { t.recordDeclaration = node } } + } else if (node is ImportDeclaration) { + // Update the imports, as they might have changed because of inference + node.updateImportedSymbols() } } diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt index 21435dddbf..059f3cbb9d 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt @@ -285,7 +285,7 @@ fun assertUsageOf(usingNode: Node?, usedNode: Node?) { fun assertUsageOfMemberAndBase(usingNode: Node?, usedBase: Node?, usedMember: Declaration?) { assertNotNull(usingNode) if (usingNode !is MemberExpression && !ENFORCE_MEMBER_EXPRESSION) { - // Assumtion here is that the target of the member portion of the expression and not the + // Assumption here is that the target of the member portion of the expression and not the // base is resolved assertUsageOf(usingNode, usedMember) } else { diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt index 063d163c2a..1b6fbfd500 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.frontends.java -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.* import com.github.javaparser.ast.expr.Expression import com.github.javaparser.resolution.UnsolvedSymbolException +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.HandlerInterface @@ -244,171 +242,31 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : expr: Expression ): de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression { val fieldAccessExpr = expr.asFieldAccessExpr() - var base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - // first, resolve the scope. this adds the necessary nodes, such as IDENTIFIER for the - // scope. - // it also acts as the first argument of the operator call - val scope = fieldAccessExpr.scope - if (scope.isNameExpr) { - var isStaticAccess = false - var baseType: Type - try { - val resolve = fieldAccessExpr.resolve() - if (resolve.asField().isStatic) { - isStaticAccess = true - } - baseType = this.objectType(resolve.asField().declaringType().qualifiedName) - } catch (ex: RuntimeException) { - isStaticAccess = true - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - if (typeString != null) { - baseType = this.objectType(typeString) - } else { - // try to get the name - val name: String - val tokenRange = scope.asNameExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asNameExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 1 for {}", fieldAccessExpr) - unknownType() - } - } - } catch (ex: NoClassDefFoundError) { - isStaticAccess = true - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - if (typeString != null) { - baseType = this.objectType(typeString) - } else { - val name: String - val tokenRange = scope.asNameExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asNameExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 1 for {}", fieldAccessExpr) - unknownType() - } - } - } - base = newReference(scope.asNameExpr().nameAsString, baseType, rawNode = scope) - base.isStaticAccess = isStaticAccess - } else if (scope.isFieldAccessExpr) { - base = - handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - ?: newProblemExpression("Could not parse base") - var tester = base - while (tester is MemberExpression) { - // we need to check if any base is only a static access, otherwise, this is a member - // access - // to this base - tester = tester.base - } - if (tester is Reference && tester.isStaticAccess) { - // try to get the name - val name: String - val tokenRange = scope.asFieldAccessExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asFieldAccessExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - val baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 2 for {}", fieldAccessExpr) - unknownType() - } - base = - newReference(scope.asFieldAccessExpr().nameAsString, baseType, rawNode = scope) - base.isStaticAccess = true - } - } else { - base = - handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - ?: newProblemExpression("Could not parse base") - } - var fieldType: Type? + + var baseType = unknownType() + var fieldType = unknownType() + // We can "try" to resolve the field using JavaParser's logic. The main reason we WANT to do + // this is to get information about system types, as long as we are not fully doing that on + // our own. try { val symbol = fieldAccessExpr.resolve() - fieldType = - frontend.typeManager.getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.asField().type.describe() - ) - if (fieldType == null) { - fieldType = frontend.typeOf(symbol.asField().type) + fieldType = frontend.typeOf(symbol.type) + + if (symbol.isField) { + baseType = objectType(symbol.asField().declaringType().qualifiedName) } - } catch (ex: RuntimeException) { - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - fieldType = - if (typeString != null) { - this.objectType(typeString) - } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.primitiveType("int") - } else { - log.info("Unknown field type for {}", fieldAccessExpr) - unknownType() - } - val memberExpression = - newMemberExpression( - fieldAccessExpr.name.identifier, - base, - fieldType, - ".", // there is only "." in java - rawNode = expr - ) - memberExpression.isStaticAccess = true - return memberExpression - } catch (ex: NoClassDefFoundError) { - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - fieldType = - if (typeString != null) { - this.objectType(typeString) - } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.primitiveType("int") - } else { - log.info("Unknown field type for {}", fieldAccessExpr) - unknownType() - } - val memberExpression = - newMemberExpression( - fieldAccessExpr.name.identifier, - base, - fieldType, - ".", - rawNode = expr - ) - memberExpression.isStaticAccess = true - return memberExpression - } - if (base.location == null) { - base.location = frontend.locationOf(fieldAccessExpr) - } + } catch (_: UnsolvedSymbolException) {} + + var base = + handle(fieldAccessExpr.scope) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + base.type = baseType return newMemberExpression( fieldAccessExpr.name.identifier, base, fieldType, - ".", - rawNode = expr + operatorCode = ".", + rawNode = fieldAccessExpr ) } @@ -502,111 +360,22 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : ): de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? { val nameExpr = expr.asNameExpr() - // TODO this commented code breaks field accesses to fields that don't have a primitive - // type. - // How should this be handled correctly? - // try { - // ResolvedType resolvedType = nameExpr.calculateResolvedType(); - // if (resolvedType.isReferenceType()) { - // return newReference( - // nameExpr.getNameAsString(), - // new Type(((ReferenceTypeImpl) resolvedType).getQualifiedName()), - // nameExpr.toString()); - // } - // } catch ( - // UnsolvedSymbolException - // e) { // this might throw, e.g. if the type is simply not defined (i.e., syntax - // error) - // return newReference( - // nameExpr.getNameAsString(), new Type(UNKNOWN_TYPE), nameExpr.toString()); - // } - val name = this.parseName(nameExpr.nameAsString) - return try { + // Try to resolve it. We will remove this in a future where we do not really in the + // javaparser symbols anymore. This is mainly needed to resolve implicit "this.field" access + // as well as access to static fields of other classes - which we could resolve once we + // fully leverage the import system in the Java frontend. + try { val symbol = nameExpr.resolve() if (symbol.isField) { val field = symbol.asField() - if (!field.isStatic) { - // convert to FieldAccessExpr - val fieldAccessExpr = FieldAccessExpr(ThisExpr(), field.name) - expr.range.ifPresent { range: Range? -> fieldAccessExpr.setRange(range) } - expr.tokenRange.ifPresent { tokenRange: TokenRange? -> - fieldAccessExpr.setTokenRange(tokenRange) - } - expr.parentNode.ifPresent { newParentNode: Node? -> - fieldAccessExpr.setParentNode(newParentNode) - } - expr.replace(fieldAccessExpr) - fieldAccessExpr.parentNode.ifPresent { newParentNode: Node? -> - expr.setParentNode(newParentNode) - } - - // handle it as a field expression - handle(fieldAccessExpr) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - } else { - val fieldAccessExpr = - FieldAccessExpr(NameExpr(field.declaringType().className), field.name) - expr.range.ifPresent { range: Range? -> fieldAccessExpr.setRange(range) } - expr.tokenRange.ifPresent { tokenRange: TokenRange? -> - fieldAccessExpr.setTokenRange(tokenRange) - } - expr.parentNode.ifPresent { newParentNode: Node? -> - fieldAccessExpr.setParentNode(newParentNode) - } - expr.replace(fieldAccessExpr) - fieldAccessExpr.parentNode.ifPresent { newParentNode: Node? -> - expr.setParentNode(newParentNode) - } - - // handle it as a field expression - handle(fieldAccessExpr) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - } - } else { - // Resolve type first with ParameterizedType - var type: Type? = - frontend.typeManager.getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.type.describe() - ) - if (type == null) { - type = frontend.typeOf(symbol.type) - } - newReference(symbol.name, type, rawNode = nameExpr) - } - } catch (ex: UnsolvedSymbolException) { - val typeString: String? = - if ( - ex.name.startsWith( - "We are unable to find the value declaration corresponding to" - ) - ) { - nameExpr.nameAsString - } else { - frontend.recoverTypeFromUnsolvedException(ex) - } - val t: Type - if (typeString == null) { - t = unknownType() - } else { - t = this.objectType(typeString) - t.typeOrigin = Type.Origin.GUESSED - } - val declaredReferenceExpression = newReference(name, t, rawNode = nameExpr) - val recordDeclaration = frontend.scopeManager.currentRecord - if (recordDeclaration != null && recordDeclaration.name.lastPartsMatch(name)) { - declaredReferenceExpression.refersTo = recordDeclaration + // handle it as a field expression + return handle(field.toFieldAccessExpr(nameExpr)) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? } - declaredReferenceExpression - } catch (ex: RuntimeException) { - val t = unknownType() - log.info("Unresolved symbol: {}", nameExpr.nameAsString) - newReference(nameExpr.nameAsString, t, rawNode = nameExpr) - } catch (ex: NoClassDefFoundError) { - val t = unknownType() - log.info("Unresolved symbol: {}", nameExpr.nameAsString) - newReference(nameExpr.nameAsString, t, rawNode = nameExpr) - } + } catch (_: UnsolvedSymbolException) {} + + val name = this.parseName(nameExpr.nameAsString) + return newReference(name, rawNode = expr) } private fun handleInstanceOfExpression(expr: Expression): BinaryOperator { @@ -889,3 +658,21 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } } } + +fun ResolvedFieldDeclaration.toFieldAccessExpr(expr: NameExpr): FieldAccessExpr { + // Convert to FieldAccessExpr + val fieldAccessExpr = + if (this.isStatic) { + FieldAccessExpr(NameExpr(this.declaringType().className), this.name) + } else { + FieldAccessExpr(ThisExpr(), this.name) + } + + expr.range.ifPresent { fieldAccessExpr.setRange(it) } + expr.tokenRange.ifPresent { fieldAccessExpr.setTokenRange(it) } + expr.parentNode.ifPresent { fieldAccessExpr.setParentNode(it) } + expr.replace(fieldAccessExpr) + fieldAccessExpr.parentNode.ifPresent { expr.setParentNode(it) } + + return fieldAccessExpr +} diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt index 78609f9b7e..87769aa050 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt @@ -63,6 +63,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.CommonPath import de.fraunhofer.aisec.cpg.passes.JavaExternalTypeHierarchyResolver +import de.fraunhofer.aisec.cpg.passes.JavaExtraPass import de.fraunhofer.aisec.cpg.passes.JavaImportResolver import de.fraunhofer.aisec.cpg.passes.configuration.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -78,6 +79,7 @@ import kotlin.jvm.optionals.getOrNull JavaExternalTypeHierarchyResolver::class ) // this pass is always required for Java @RegisterExtraPass(JavaImportResolver::class) +@RegisterExtraPass(JavaExtraPass::class) open class JavaLanguageFrontend(language: Language, ctx: TranslationContext) : LanguageFrontend(language, ctx) { @@ -141,6 +143,12 @@ open class JavaLanguageFrontend(language: Language, ctx: T scopeManager.addDeclaration(incl) } + // We create an implicit import for "java.lang.*" + val decl = + newImportDeclaration(parseName("java.lang"), wildcardImport = true) + .implicit("import java.lang.*") + scopeManager.addDeclaration(decl) + if (namespaceDeclaration != null) { scopeManager.leaveScope(namespaceDeclaration) } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt new file mode 100644 index 0000000000..7984ca5dfc --- /dev/null +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, 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.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.fqn +import de.fraunhofer.aisec.cpg.graph.newReference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.helpers.replace +import de.fraunhofer.aisec.cpg.nameIsType +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore + +@DependsOn(TypeResolver::class) +@ExecuteBefore(SymbolResolver::class) +class JavaExtraPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + private lateinit var walker: SubgraphWalker.ScopedWalker + + override fun accept(tu: TranslationUnitDeclaration) { + // Loop through all member expressions + walker = SubgraphWalker.ScopedWalker(ctx.scopeManager) + walker.registerHandler { _, parent, node -> + when (node) { + is MemberExpression -> handleMemberExpression(node, parent) + } + } + + walker.iterate(tu) + } + + fun handleMemberExpression(me: MemberExpression, parent: Node?) { + // For now, we are only interested in fields and not in calls, since this will open another + // can of worms + if (parent is CallExpression && parent.callee == me) return + + // Look at the "base" of the member expression and check if this is referring to a type + var base = me.base as? Reference + var type = base?.nameIsType() + if (type != null) { + // Our base refers to a type, so this is actually a static reference, which we + // model not as a member expression, but as a reference with an FQN. Let's build that + // and exchange the node + val ref = + newReference(type.name.fqn(me.name.localName), type = me.type) + .codeAndLocationFrom(me) + .apply { isStaticAccess = true } + walker.replace(parent, me, ref) + } + } + + override fun cleanup() { + // Nothing to do + } +} diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt index ca83ba515f..f7fc72b283 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt @@ -89,13 +89,16 @@ internal class SuperCallTest : BaseTest() { val methods = subClass.methods val field = findByUniqueName(subClass.fields, "field") val getField = findByUniqueName(methods, "getField") - var refs = getField.allChildren() + var refs = getField.refs val fieldRef = findByUniquePredicate(refs) { "field" == it.code } val getSuperField = findByUniqueName(methods, "getSuperField") refs = getSuperField.allChildren() val superFieldRef = findByUniquePredicate(refs) { "super.field" == it.code } - assertTrue(fieldRef.base is Reference) - assertRefersTo(fieldRef.base, getField.receiver) + // See https://github.com/Fraunhofer-AISEC/cpg/issues/1863. + // We only have a regular call expression here, if we do not resolve anything else in the + // frontend + /*assertTrue(fieldRef.base is Reference) + assertRefersTo(fieldRef.base, getField.receiver)*/ assertEquals(field, fieldRef.refersTo) assertTrue(superFieldRef.base is Reference) assertRefersTo(superFieldRef.base, getSuperField.receiver) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt index 0def824fc9..df6b99abb5 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt @@ -112,17 +112,19 @@ internal class StaticImportsTest : BaseTest() { } } val testFields = a.fields - val staticField = findByUniqueName(testFields, "staticField") - val nonStaticField = findByUniqueName(testFields, "nonStaticField") + val staticField = a.fields["staticField"] + val inferredNonStaticField = b.fields["nonStaticField"] + assertNotNull(staticField) + assertNotNull(inferredNonStaticField) assertTrue(staticField.modifiers.contains("static")) - assertFalse(nonStaticField.modifiers.contains("static")) + assertFalse(inferredNonStaticField.modifiers.contains("static")) - val declaredReferences = main.allChildren() + val declaredReferences = main.refs val usage = findByUniqueName(declaredReferences, "staticField") - assertEquals(staticField, usage.refersTo) + assertRefersTo(usage, staticField) val nonStatic = findByUniqueName(declaredReferences, "nonStaticField") - assertNotEquals(nonStaticField, nonStatic.refersTo) - assertTrue(nonStatic.refersTo!!.isInferred) + assertRefersTo(nonStatic, inferredNonStaticField) + assertTrue(nonStatic.refersTo?.isInferred == true) } }