diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index c3b82d6ea64..927e5c27b9b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -151,6 +151,17 @@ class ScopeManager(override var ctx: TranslationContext) : ScopeProvider, Contex // also update the AST node of the existing scope to the "latest" we have seen existing.astNode = entry.value.astNode + // Re-parent the children of the duplicate scope (entry.value) to the surviving + // scope (existing). Without this, inner scopes such as FunctionScopes whose + // parent still points to the stale duplicate scope would miss symbols that are + // only declared in the surviving scope when walking the parent chain during + // symbol resolution. + for (child in entry.value.children) { + child.parent = existing + existing.children.add(child) + } + entry.value.children.clear() + // now it gets more tricky. we also need to "redirect" the AST nodes in the sub // scope manager to our existing NameScope (currently, they point to their own, // invalid copy of the NameScope). diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/EdgeWalker.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/EdgeWalker.kt index d6df07a3c4d..b2889bef8fc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/EdgeWalker.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/EdgeWalker.kt @@ -56,7 +56,7 @@ inline fun > Node.edges( // Gather all edges if (obj is EdgeCollection<*, *>) { - for (edge in obj.toList()) { + for (edge in obj) { if (edge is EdgeType && predicate.invoke(edge)) { edges += edge } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt index 9c0b1a33cb9..1369b04e783 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt @@ -39,6 +39,9 @@ import de.fraunhofer.aisec.cpg.graph.expressions.Reference import de.fraunhofer.aisec.cpg.graph.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.unknownType +/** Thread-local set to track nodes currently propagating types to prevent infinite recursion */ +private val propagatingNodes = ThreadLocal.withInitial { mutableSetOf() } + /** * This interfaces denotes that the given [Node] has a "type". Currently, we only have two known * implementations of this class, an [Expression] and a [ValueDeclaration]. All other nodes with @@ -195,24 +198,35 @@ interface HasType : LanguageProvider { return } - if (changeType == TypeObserver.ChangeType.ASSIGNED_TYPE) { - val assignedTypes = this.assignedTypes - if (assignedTypes.isEmpty()) { - return - } - // Inform all type observers about the changes - for (observer in typeObservers) { - observer.assignedTypeChanged(assignedTypes, this) - } - } else { - val newType = this.type - if (newType is UnknownType) { - return - } - // Inform all type observers about the changes - for (observer in typeObservers) { - observer.typeChanged(newType, this) + // Guard against infinite recursion in type propagation cycles + val currentlyPropagating = propagatingNodes.get() + if (this in currentlyPropagating) { + return + } + + currentlyPropagating.add(this) + try { + if (changeType == TypeObserver.ChangeType.ASSIGNED_TYPE) { + val assignedTypes = this.assignedTypes + if (assignedTypes.isEmpty()) { + return + } + // Inform all type observers about the changes + for (observer in typeObservers) { + observer.assignedTypeChanged(assignedTypes, this) + } + } else { + val newType = this.type + if (newType is UnknownType) { + return + } + // Inform all type observers about the changes + for (observer in typeObservers) { + observer.typeChanged(newType, this) + } } + } finally { + currentlyPropagating.remove(this) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt index c1a730c84e6..3cd3cef6e32 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt @@ -104,5 +104,10 @@ class PointerType : Type, SecondOrderType { pointerOrigin == other.pointerOrigin } - override fun hashCode() = Objects.hash(super.hashCode(), elementType, pointerOrigin) + override fun hashCode(): Int { + return Objects.hash(super.hashCode(), elementType, pointerOrigin) + // TODO: Use System.identityHashCode for elementType to avoid infinite recursion + // when dealing with circular type references (e.g., struct with pointer to itself). + // However, this needs a re-design of the current type handling + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt index 1bb61b53731..f9f2f20074a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt @@ -125,7 +125,11 @@ object SubgraphWalker { when (obj) { is EdgeCollection<*, *> -> { - obj.toNodeCollection({ it is AstEdge<*> }).filterIsInstanceTo(children) + for (edge in obj) { + if (edge is AstEdge<*>) { + children.add(edge.end) + } + } } else -> { throw AnnotationFormatError( 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 f8efcb5e68b..1180ee6f9f9 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 @@ -49,6 +49,7 @@ import de.fraunhofer.aisec.cpg.passes.inference.tryFunctionInferenceFromFunction import de.fraunhofer.aisec.cpg.passes.inference.tryVariableInference import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import java.util.IdentityHashMap import kotlin.collections.firstOrNull import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -721,7 +722,7 @@ open class SymbolResolver(ctx: TranslationContext) : EOGStarterPass(ctx) { companion object { val LOGGER: Logger = LoggerFactory.getLogger(SymbolResolver::class.java) - val componentsToTemplates = mutableMapOf>() + val componentsToTemplates = IdentityHashMap>() /** * Adds implicit duplicates of the TemplateParams to the implicit Construction diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt index 840025feb84..62e5634411c 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.frontends.golang 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.frontends.SupportsNewParse import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.frontends.golang.GoStandardLibrary.Modfile @@ -52,6 +53,7 @@ import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File import java.net.URI +import java.nio.file.Path /** * A language frontend for the [GoLanguage]. It makes use the internal @@ -65,9 +67,10 @@ import java.net.URI old = EvaluationOrderGraphPass::class, with = GoEvaluationOrderGraphPass::class, ) -@SupportsParallelParsing(false) +@SupportsParallelParsing(true) class GoLanguageFrontend(ctx: TranslationContext, language: Language) : - LanguageFrontend(ctx, language) { + LanguageFrontend(ctx, language), + SupportsNewParse { private var currentFileSet: GoStandardLibrary.Ast.FileSet? = null private var currentModule: GoStandardLibrary.Modfile.File? = null @@ -253,6 +256,17 @@ class GoLanguageFrontend(ctx: TranslationContext, language: Language assertNotNull(nil) @@ -243,7 +240,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(o) assertLocalName("MyStruct[]", o.type) - val myStruct = tu.records["MyStruct"] + val myStruct = result.records["MyStruct"] assertNotNull(myStruct) val field = myStruct.fields["Field"] @@ -277,7 +274,7 @@ class GoLanguageFrontendTest : BaseTest() { assertRefersTo(keyValue.key, field) assertLiteralValue(10, keyValue.value) - val rr = tu.variables["rr"] + val rr = result.variables["rr"] assertNotNull(rr) var init = rr.initializer @@ -293,7 +290,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(zero) assertRefersTo(key, zero) - val mapr = tu.variables["mapr"] + val mapr = result.variables["mapr"] assertNotNull(mapr) init = mapr.initializer @@ -968,8 +965,8 @@ class GoLanguageFrontendTest : BaseTest() { fun testResolveStdLibImport() { val stdLib = Path.of("src", "test", "resources", "golang-std") val topLevel = Path.of("src", "test", "resources", "golang") - val tu = - analyzeAndGetFirstTU( + val result = + analyze( listOf(topLevel.resolve("function.go").toFile(), stdLib.resolve("fmt").toFile()), topLevel, true, @@ -978,9 +975,7 @@ class GoLanguageFrontendTest : BaseTest() { it.includePath(stdLib) } - assertNotNull(tu) - - val p = tu.namespaces["p"] + val p = result.namespaces["p"] assertNotNull(p) val main = p.functions["main"] @@ -1018,7 +1013,6 @@ class GoLanguageFrontendTest : BaseTest() { analyze(files, topLevel, true) { it.registerLanguage() it.includePath(stdLib) - it.useParallelFrontends(false) it.symbols(mapOf("GOOS" to "darwin", "GOARCH" to "arm64")) }