Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ inline fun <reified EdgeType : Edge<out Node>> 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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HasType>() }

/**
* 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
Expand Down Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -721,7 +722,7 @@ open class SymbolResolver(ctx: TranslationContext) : EOGStarterPass(ctx) {
companion object {
val LOGGER: Logger = LoggerFactory.getLogger(SymbolResolver::class.java)

val componentsToTemplates = mutableMapOf<Component, MutableList<Template>>()
val componentsToTemplates = IdentityHashMap<Component, MutableList<Template>>()

/**
* Adds implicit duplicates of the TemplateParams to the implicit Construction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -65,9 +67,10 @@ import java.net.URI
old = EvaluationOrderGraphPass::class,
with = GoEvaluationOrderGraphPass::class,
)
@SupportsParallelParsing(false)
@SupportsParallelParsing(true)
class GoLanguageFrontend(ctx: TranslationContext, language: Language<GoLanguageFrontend>) :
LanguageFrontend<GoStandardLibrary.Ast.Node, GoStandardLibrary.Ast.Expr>(ctx, language) {
LanguageFrontend<GoStandardLibrary.Ast.Node, GoStandardLibrary.Ast.Expr>(ctx, language),
SupportsNewParse {

private var currentFileSet: GoStandardLibrary.Ast.FileSet? = null
private var currentModule: GoStandardLibrary.Modfile.File? = null
Expand Down Expand Up @@ -253,6 +256,17 @@ class GoLanguageFrontend(ctx: TranslationContext, language: Language<GoLanguageF
return tu
}

@Throws(TranslationException::class)
override fun parse(content: String, path: Path?): TranslationUnit {
// Delegate to the file-based parse method
// The Go parser reads from the file directly, so we need the path
return if (path != null) {
parse(path.toFile())
} else {
throw TranslationException("Go frontend requires a file path for parsing")
}
}

override fun typeOf(type: GoStandardLibrary.Ast.Expr): Type {
val cpgType =
when (type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,37 +190,34 @@ class GoLanguageFrontendTest : BaseTest() {
}
assertNotNull(result)

val tu = result.components["application"]?.translationUnits?.firstOrNull()
assertNotNull(tu)

val p = tu.namespaces["p"]
val p = result.namespaces["p"]
assertNotNull(p)

val a = p.variables["a"]
assertNotNull(a)
assertNotNull(a.location)

assertLocalName("a", a)
assertEquals(tu.primitiveType("int"), a.type)
assertEquals(result.primitiveType("int"), a.type)

val s = p.variables["s"]
assertNotNull(s)
assertLocalName("s", s)
assertEquals(tu.primitiveType("string"), s.type)
assertEquals(result.primitiveType("string"), s.type)

val f = p.variables["f"]
assertNotNull(f)
assertLocalName("f", f)
assertEquals(tu.primitiveType("float64"), f.type)
assertEquals(result.primitiveType("float64"), f.type)

val f32 = p.variables["f32"]
assertNotNull(f32)
assertLocalName("f32", f32)
assertEquals(tu.primitiveType("float32"), f32.type)
assertEquals(result.primitiveType("float32"), f32.type)

val n = p.variables["n"]
assertNotNull(n)
assertEquals(tu.primitiveType("int").pointer(), n.type)
assertEquals(result.primitiveType("int").pointer(), n.type)

val nil = n.initializer as? Literal<*>
assertNotNull(nil)
Expand All @@ -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"]
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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"]
Expand Down Expand Up @@ -1018,7 +1013,6 @@ class GoLanguageFrontendTest : BaseTest() {
analyze(files, topLevel, true) {
it.registerLanguage<GoLanguage>()
it.includePath(stdLib)
it.useParallelFrontends(false)
it.symbols(mapOf("GOOS" to "darwin", "GOARCH" to "arm64"))
}

Expand Down
Loading